def get_coordinates(image_size, search_area_size, window_size, overlap): """Compute the x, y coordinates of the centers of the interrogation windows. Parameters ---------- image_size: two elements tuple a three dimensional tuple for the pixel size of the image window_size: tuple the size of the interrogation window. search_area_size: tuple the size of the search area window. overlap: tuple the number of pixel by which two adjacent interrogation windows overlap. Returns ------- x : 23 np.ndarray a three dimensional array containing the x coordinates of the interrogation window centers, in pixels. y : 23 np.ndarray a three dimensional array containing the y coordinates of the interrogation window centers, in pixels. z : 23 np.ndarray a three dimensional array containing the y coordinates of the interrogation window centers, in pixels. """ # get shape of the resulting flow field field_shape = get_field_shape(image_size, search_area_size, overlap) # compute grid coordinates of the search area centers x = (np.arange(field_shape[1]) * (window_size[1] - overlap[1]) + (search_area_size[1] - 1) / 2.0) y = (np.arange(field_shape[0]) * (window_size[0] - overlap[0]) + (search_area_size[0] - 1) / 2.0) z = (np.arange(field_shape[2]) * (window_size[2] - overlap[2]) + (search_area_size[2] - 1) / 2.0) # moving coordinates further to the center, so that the points at the extreme left/right or top/bottom # have the same distance to the window edges. For simplicity only integer movements are allowed. x += (image_size[1] - 1 - ((field_shape[1] - 1) * (window_size[1] - overlap[1]) + (search_area_size[1] - 1))) // 2 y += (image_size[0] - 1 - ((field_shape[0] - 1) * (window_size[0] - overlap[0]) + (search_area_size[0] - 1))) // 2 z += (image_size[2] - 1 - ((field_shape[2] - 1) * (window_size[2] - overlap[2]) + (search_area_size[2] - 1))) // 2 return np.meshgrid(x, y, z)
def get_coordinates(image_size, window_size, overlap): """Compute the x, y coordinates of the centers of the interrogation windows. Parameters ---------- image_size: two elements tuple a two dimensional tuple for the pixel size of the image first element is number of rows, second element is the number of columns. window_size: int the size of the interrogation windows. overlap: int the number of pixel by which two adjacent interrogation windows overlap. Returns ------- x : 2d np.ndarray a two dimensional array containing the x coordinates of the interrogation window centers, in pixels. y : 2d np.ndarray a two dimensional array containing the y coordinates of the interrogation window centers, in pixels. """ # get shape of the resulting flow field '''%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The get_field_shape function calculates how many interrogation windows fit in the image in each dimension output is a tuple (amount of interrogation windows in y, amount of interrogation windows in x) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The get coordinates function calculates the coordinates of the center of each interrogation window using bases on the to field_shape returned by the get field_shape function, the window size and the overlap. It returns a meshgrid of the interrogation area centers. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ''' field_shape = pyprocess.get_field_shape(image_size, window_size, overlap) # compute grid coordinates of the interrogation window centers x = np.arange( field_shape[1]) * (window_size - overlap) + (window_size) / 2.0 y = np.arange( field_shape[0]) * (window_size - overlap) + (window_size) / 2.0 return np.meshgrid(x, y[::-1])
def run_piv( frame_a, frame_b, winsize=16, # pixels, interrogation window size in frame A searchsize=20, # pixels, search in image B overlap=8, # pixels, 50% overlap dt=0.0001, # sec, time interval between pulses image_check=False, show_vertical_profiles=False, figure_export_name='_results.png', text_export_name="_results.txt", scale_factor=1, pixel_density=36.74, arrow_width=0.001, show_result=True, u_bounds=(-100, 100), v_bounds=(-100, 100)): u0, v0, sig2noise = pyprocess.extended_search_area_piv( frame_a.astype(np.int32), frame_b.astype(np.int32), window_size=winsize, overlap=overlap, dt=dt, search_area_size=searchsize, sig2noise_method='peak2peak') x, y = pyprocess.get_coordinates(image_size=frame_a.shape, search_area_size=searchsize, overlap=overlap) x, y, u0, v0 = scaling.uniform( x, y, u0, v0, scaling_factor=pixel_density) # no. pixel per distance u0, v0, mask = validation.global_val(u0, v0, u_bounds, v_bounds) u1, v1, mask = validation.sig2noise_val(u0, v0, sig2noise, threshold=1.05) u3, v3 = filters.replace_outliers(u1, v1, method='localmean', max_iter=10, kernel_size=3) #save in the simple ASCII table format if np.std(u3) < 480: tools.save(x, y, u3, v3, sig2noise, mask, text_export_name) if image_check == True: fig, ax = plt.subplots(2, 1, figsize=(24, 12)) ax[0].imshow(frame_a) ax[1].imshow(frame_b) io.imwrite(figure_export_name, frame_a) if show_result == True: fig, ax = plt.subplots(figsize=(24, 12)) tools.display_vector_field( text_export_name, ax=ax, scaling_factor=pixel_density, scale=scale_factor, # scale defines here the arrow length width=arrow_width, # width is the thickness of the arrow on_img=True, # overlay on the image image_name=figure_export_name) fig.savefig(figure_export_name) if show_vertical_profiles: field_shape = pyprocess.get_field_shape(image_size=frame_a.shape, search_area_size=searchsize, overlap=overlap) vertical_profiles(text_export_name, field_shape) print('Std of u3: %.3f' % np.std(u3)) print('Mean of u3: %.3f' % np.mean(u3)) return np.std(u3)
def quick_piv(self, search_dict, index_a=100, index_b=101, folder=None): self.show_piv_param() ns = Namespace(**self.piv_param) if folder == None: img_a, img_b = self.read_two_images(search_dict, index_a=index_a, index_b=index_b) location_path = [ x['path'] for x in self.piv_dict_list if search_dict.items() <= x.items() ] results_path = os.path.join(self.results_path, *location_path) try: os.makedirs(results_path) except FileExistsError: pass else: try: file_a_path = os.path.join(self.path, folder, 'frame_%06d.tiff' % index_a) file_b_path = os.path.join(self.path, folder, 'frame_%06d.tiff' % index_b) img_a = np.array(Image.open(file_a_path)) img_b = np.array(Image.open(file_b_path)) except: return None # crop img_a = img_a[ns.crop[0]:-ns.crop[1] - 1, ns.crop[2]:-ns.crop[3] - 1] img_b = img_b[ns.crop[0]:-ns.crop[1] - 1, ns.crop[2]:-ns.crop[3] - 1] u0, v0, sig2noise = pyprocess.extended_search_area_piv( img_a.astype(np.int32), img_b.astype(np.int32), window_size=ns.winsize, overlap=ns.overlap, dt=ns.dt, search_area_size=ns.searchsize, sig2noise_method='peak2peak') x, y = pyprocess.get_coordinates(image_size=img_a.shape, search_area_size=ns.searchsize, overlap=ns.overlap) x, y, u0, v0 = scaling.uniform( x, y, u0, v0, scaling_factor=ns.pixel_density) # no. pixel per distance u0, v0, mask = validation.global_val( u0, v0, (ns.u_lower_bound, ns.u_upper_bound), (ns.v_lower_bound, ns.v_upper_bound)) u1, v1, mask = validation.sig2noise_val(u0, v0, sig2noise, threshold=1.01) u3, v3 = filters.replace_outliers(u1, v1, method='localmean', max_iter=500, kernel_size=3) #save in the simple ASCII table format tools.save(x, y, u3, v3, sig2noise, mask, os.path.join(results_path, ns.text_export_name)) if ns.image_check == True: fig, ax = plt.subplots(2, 1, figsize=(24, 12)) ax[0].imshow(img_a) ax[1].imshow(img_b) io.imwrite(os.path.join(results_path, ns.figure_export_name), img_a) if ns.show_result == True: fig, ax = plt.subplots(figsize=(24, 12)) tools.display_vector_field( os.path.join(results_path, ns.text_export_name), ax=ax, scaling_factor=ns.pixel_density, scale=ns.scale_factor, # scale defines here the arrow length width=ns.arrow_width, # width is the thickness of the arrow on_img=True, # overlay on the image image_name=os.path.join(results_path, ns.figure_export_name)) fig.savefig(os.path.join(results_path, ns.figure_export_name)) if ns.show_vertical_profiles: field_shape = pyprocess.get_field_shape( image_size=img_a.shape, search_area_size=ns.searchsize, overlap=ns.overlap) vertical_profiles(ns.text_export_name, field_shape) print('Mean of u: %.3f' % np.mean(u3)) print('Std of u: %.3f' % np.std(u3)) print('Mean of v: %.3f' % np.mean(v3)) print('Std of v: %.3f' % np.std(v3)) output = np.array([np.mean(u3), np.std(u3), np.mean(v3), np.std(v3)]) # if np.absolute(np.mean(v3)) < 50: # output = self.quick_piv(search_dict,index_a = index_a + 1, index_b = index_b + 1) return x, y, u3, v3
def multipass_img_deform( frame_a, frame_b, current_iteration, x_old, y_old, u_old, v_old, settings, mask_coords=[], ): # window_size, # overlap, # iterations, # current_iteration, # x_old, # y_old, # u_old, # v_old, # correlation_method="circular", # normalized_correlation=False, # subpixel_method="gaussian", # deformation_method="symmetric", # sig2noise_method="peak2peak", # sig2noise_threshold=1.0, # sig2noise_mask=2, # interpolation_order=1, """ Multi pass of the PIV evaluation. This function does the PIV evaluation of the second and other passes. It returns the coordinates of the interrogation window centres, the displacement u, v for each interrogation window as well as the signal to noise ratio array (which is full of NaNs if opted out) Parameters ---------- frame_a : 2d np.ndarray the first image frame_b : 2d np.ndarray the second image window_size : tuple of ints the size of the interrogation window overlap : tuple of ints the overlap of the interrogation window, e.g. window_size/2 x_old : 2d np.ndarray the x coordinates of the vector field of the previous pass y_old : 2d np.ndarray the y coordinates of the vector field of the previous pass u_old : 2d np.ndarray the u displacement of the vector field of the previous pass in case of the image mask - u_old and v_old are MaskedArrays v_old : 2d np.ndarray the v displacement of the vector field of the previous pass subpixel_method: string the method used for the subpixel interpolation. one of the following methods to estimate subpixel location of the peak: 'centroid' [replaces default if correlation map is negative], 'gaussian' [default if correlation map is positive], 'parabolic' interpolation_order : int the order of the spline interpolation used for the image deformation mask_coords : list of x,y coordinates (pixels) of the image mask, default is an empty list Returns ------- x : 2d np.array array containg the x coordinates of the interrogation window centres y : 2d np.array array containg the y coordinates of the interrogation window centres u : 2d np.array array containing the horizontal displacement for every interrogation window [pixels] u : 2d np.array array containing the vertical displacement for every interrogation window it returns values in [pixels] s2n : 2D np.array of signal to noise ratio values """ if not isinstance(u_old, np.ma.MaskedArray): raise ValueError('Expected masked array') # calculate the y and y coordinates of the interrogation window centres. # Hence, the # edges must be extracted to provide the sufficient input. x_old and y_old # are the coordinates of the old grid. x_int and y_int are the coordinates # of the new grid window_size = settings.windowsizes[current_iteration] overlap = settings.overlap[current_iteration] x, y = get_coordinates(frame_a.shape, window_size, overlap) # The interpolation function dont like meshgrids as input. # plus the coordinate system for y is now from top to bottom # and RectBivariateSpline wants an increasing set y_old = y_old[:, 0] # y_old = y_old[::-1] x_old = x_old[0, :] y_int = y[:, 0] # y_int = y_int[::-1] x_int = x[0, :] # interpolating the displacements from the old grid onto the new grid # y befor x because of numpy works row major ip = RectBivariateSpline(y_old, x_old, u_old.filled(0.)) u_pre = ip(y_int, x_int) ip2 = RectBivariateSpline(y_old, x_old, v_old.filled(0.)) v_pre = ip2(y_int, x_int) # if settings.show_plot: if settings.show_all_plots: plt.figure() plt.quiver(x_old, y_old, u_old, -1 * v_old, color='b') plt.quiver(x_int, y_int, u_pre, -1 * v_pre, color='r', lw=2) plt.gca().set_aspect(1.) plt.gca().invert_yaxis() plt.title('inside deform, invert') plt.show() # @TKauefer added another method to the windowdeformation, 'symmetric' # splits the onto both frames, takes more effort due to additional # interpolation however should deliver better results old_frame_a = frame_a.copy() old_frame_b = frame_b.copy() # Image deformation has to occur in image coordinates # therefore we need to convert the results of the # previous pass which are stored in the physical units # and so y from the get_coordinates if settings.deformation_method == "symmetric": # this one is doing the image deformation (see above) x_new, y_new, ut, vt = create_deformation_field( frame_a, x, y, u_pre, v_pre) frame_a = scn.map_coordinates(frame_a, ((y_new - vt / 2, x_new - ut / 2)), order=settings.interpolation_order, mode='nearest') frame_b = scn.map_coordinates(frame_b, ((y_new + vt / 2, x_new + ut / 2)), order=settings.interpolation_order, mode='nearest') elif settings.deformation_method == "second image": frame_b = deform_windows( frame_b, x, y, u_pre, -v_pre, interpolation_order=settings.interpolation_order) else: raise Exception("Deformation method is not valid.") # if settings.show_plot: if settings.show_all_plots: if settings.deformation_method == 'symmetric': plt.figure() plt.imshow(frame_a - old_frame_a) plt.show() plt.figure() plt.imshow(frame_b - old_frame_b) plt.show() # if do_sig2noise is True # sig2noise_method = sig2noise_method # else: # sig2noise_method = None # so we use here default circular not normalized correlation: # if we did not want to validate every step, remove the method if settings.sig2noise_validate is False: settings.sig2noise_method = None u, v, s2n = extended_search_area_piv( frame_a, frame_b, window_size=window_size, overlap=overlap, width=settings.sig2noise_mask, subpixel_method=settings.subpixel_method, sig2noise_method=settings.sig2noise_method, correlation_method=settings.correlation_method, normalized_correlation=settings.normalized_correlation, ) shapes = np.array(get_field_shape(frame_a.shape, window_size, overlap)) u = u.reshape(shapes) v = v.reshape(shapes) s2n = s2n.reshape(shapes) u += u_pre v += v_pre # reapply the image mask to the new grid if settings.image_mask: grid_mask = preprocess.prepare_mask_on_grid(x, y, mask_coords) u = np.ma.masked_array(u, mask=grid_mask) v = np.ma.masked_array(v, mask=grid_mask) else: u = np.ma.masked_array(u, np.ma.nomask) v = np.ma.masked_array(v, np.ma.nomask) # validate in the multi-pass by default u, v, mask = validation.typical_validation(u, v, s2n, settings) if np.all(mask): raise ValueError("Something happened in the validation") if not isinstance(u, np.ma.MaskedArray): raise ValueError('not a masked array anymore') if settings.show_all_plots: plt.figure() nans = np.nonzero(mask) plt.quiver(x[~nans], y[~nans], u[~nans], -v[~nans], color='b') plt.quiver(x[nans], y[nans], u[nans], -v[nans], color='r') plt.gca().invert_yaxis() plt.gca().set_aspect(1.) plt.title('After sig2noise, inverted') plt.show() # we have to replace outliers u, v = filters.replace_outliers( u, v, method=settings.filter_method, max_iter=settings.max_filter_iteration, kernel_size=settings.filter_kernel_size, ) # reapply the image mask to the new grid if settings.image_mask: grid_mask = preprocess.prepare_mask_on_grid(x, y, mask_coords) u = np.ma.masked_array(u, mask=grid_mask) v = np.ma.masked_array(v, mask=grid_mask) else: u = np.ma.masked_array(u, np.ma.nomask) v = np.ma.masked_array(v, np.ma.nomask) if settings.show_all_plots: plt.figure() plt.quiver(x, y, u, -v, color='r') plt.quiver(x, y, u_pre, -1 * v_pre, color='b') plt.gca().invert_yaxis() plt.gca().set_aspect(1.) plt.title(' after replaced outliers, red, invert') plt.show() return x, y, u, v, s2n, mask
def first_pass(frame_a, frame_b, settings): # window_size, # overlap, # iterations, # correlation_method="circular", # normalized_correlation=False, # subpixel_method="gaussian", # do_sig2noise=False, # sig2noise_method="peak2peak", # sig2noise_mask=2, # settings): """ First pass of the PIV evaluation. This function does the PIV evaluation of the first pass. It returns the coordinates of the interrogation window centres, the displacment u and v for each interrogation window as well as the mask which indicates wether the displacement vector was interpolated or not. Parameters ---------- frame_a : 2d np.ndarray the first image frame_b : 2d np.ndarray the second image window_size : int the size of the interrogation window overlap : int the overlap of the interrogation window, typically it is window_size/2 subpixel_method: string the method used for the subpixel interpolation. one of the following methods to estimate subpixel location of the peak: 'centroid' [replaces default if correlation map is negative], 'gaussian' [default if correlation map is positive], 'parabolic' Returns ------- x : 2d np.array array containg the x coordinates of the interrogation window centres y : 2d np.array array containg the y coordinates of the interrogation window centres u : 2d np.array array containing the u displacement for every interrogation window u : 2d np.array array containing the u displacement for every interrogation window """ # if do_sig2noise is False or iterations != 1: # sig2noise_method = None # this indicates to get out nans u, v, s2n = extended_search_area_piv( frame_a, frame_b, window_size=settings.windowsizes[0], overlap=settings.overlap[0], search_area_size=settings.windowsizes[0], width=settings.sig2noise_mask, subpixel_method=settings.subpixel_method, sig2noise_method=settings.sig2noise_method, correlation_method=settings.correlation_method, normalized_correlation=settings.normalized_correlation) shapes = np.array( get_field_shape(frame_a.shape, settings.windowsizes[0], settings.overlap[0])) u = u.reshape(shapes) v = v.reshape(shapes) s2n = s2n.reshape(shapes) x, y = get_coordinates(frame_a.shape, settings.windowsizes[0], settings.overlap[0]) return x, y, u, v, s2n
def multipass_img_deform(frame_a, frame_b, window_size, overlap, iterations, current_iteration, x_old, y_old, u_old, v_old, correlation_method='circular', subpixel_method='gaussian', do_sig2noise=False, sig2noise_method='peak2peak', sig2noise_mask=2, MinMaxU=(-100, 50), MinMaxV=(-50, 50), std_threshold=5, median_threshold=2, median_size=1, filter_method='localmean', max_filter_iteration=10, filter_kernel_size=2, interpolation_order=3): """ First pass of the PIV evaluation. This function does the PIV evaluation of the first pass. It returns the coordinates of the interrogation window centres, the displacment u and v for each interrogation window as well as the mask which indicates wether the displacement vector was interpolated or not. Parameters ---------- frame_a : 2d np.ndarray the first image frame_b : 2d np.ndarray the second image window_size : tuple of ints the size of the interrogation window overlap : tuple of ints the overlap of the interrogation window normal for example window_size/2 x_old : 2d np.ndarray the x coordinates of the vector field of the previous pass y_old : 2d np.ndarray the y coordinates of the vector field of the previous pass u_old : 2d np.ndarray the u displacement of the vector field of the previous pass v_old : 2d np.ndarray the v displacement of the vector field of the previous pass subpixel_method: string the method used for the subpixel interpolation. one of the following methods to estimate subpixel location of the peak: 'centroid' [replaces default if correlation map is negative], 'gaussian' [default if correlation map is positive], 'parabolic' MinMaxU : two elements tuple sets the limits of the u displacment component Used for validation. MinMaxV : two elements tuple sets the limits of the v displacment component Used for validation. std_threshold : float sets the threshold for the std validation median_threshold : float sets the threshold for the median validation filter_method : string the method used to replace the non-valid vectors Methods: 'localmean', 'disk', 'distance', max_filter_iteration : int maximum of filter iterations to replace nans filter_kernel_size : int size of the kernel used for the filtering interpolation_order : int the order of the spline interpolation used for the image deformation Returns ------- x : 2d np.array array containg the x coordinates of the interrogation window centres y : 2d np.array array containg the y coordinates of the interrogation window centres u : 2d np.array array containing the u displacement for every interrogation window u : 2d np.array array containing the u displacement for every interrogation window mask : 2d np.array array containg the mask values (bool) which contains information if the vector was filtered """ x, y = get_coordinates(np.shape(frame_a), window_size, overlap) 'calculate the y and y coordinates of the interrogation window centres' y_old = y_old[:, 0] # y_old = y_old[::-1] x_old = x_old[0, :] y_int = y[:, 0] # y_int = y_int[::-1] x_int = x[0, :] '''The interpolation function dont like meshgrids as input. Hence, the the edges must be extracted to provide the sufficient input. x_old and y_old are the are the coordinates of the old grid. x_int and y_int are the coordiantes of the new grid''' ip = RectBivariateSpline(y_old, x_old, u_old) u_pre = ip(y_int, x_int) ip2 = RectBivariateSpline(y_old, x_old, v_old) v_pre = ip2(y_int, x_int) ''' interpolating the displacements from the old grid onto the new grid y befor x because of numpy works row major ''' frame_b_deform = frame_interpolation( frame_b, x, y, u_pre, -v_pre, interpolation_order=interpolation_order) '''this one is doing the image deformation (see above)''' cor_win_1 = pyprocess.moving_window_array(frame_a, window_size, overlap) cor_win_2 = pyprocess.moving_window_array(frame_b_deform, window_size, overlap) '''Filling the interrogation window. They windows are arranged in a 3d array with number of interrogation window *window_size*window_size this way is much faster then using a loop''' correlation = correlation_func(cor_win_1, cor_win_2, correlation_method=correlation_method, normalized_correlation=False) 'do the correlation' disp = np.zeros((np.size(correlation, 0), 2)) for i in range(0, np.size(correlation, 0)): ''' determine the displacment on subpixel level ''' disp[i, :] = find_subpixel_peak_position( correlation[i, :, :], subpixel_method=subpixel_method) 'this loop is doing the displacment evaluation for each window ' disp = np.array(disp) - np.floor(np.array(correlation[0, :, :].shape) / 2) 'reshaping the interrogation window to vector field shape' shapes = np.array( pyprocess.get_field_shape(np.shape(frame_a), window_size, overlap)) u = disp[:, 1].reshape(shapes) v = -disp[:, 0].reshape(shapes) 'adding the recent displacment on to the displacment of the previous pass' u = u + u_pre v = v + v_pre 'validation using gloabl limits and local median' u, v, mask_g = validation.global_val(u, v, MinMaxU, MinMaxV) u, v, mask_s = validation.global_std(u, v, std_threshold=std_threshold) u, v, mask_m = validation.local_median_val(u, v, u_threshold=median_threshold, v_threshold=median_threshold, size=median_size) mask = mask_g + mask_m + mask_s 'adding masks to add the effect of alle the validations' #mask=np.zeros_like(u) 'filter to replace the values that where marked by the validation' if current_iteration != iterations: 'filter to replace the values that where marked by the validation' u, v = filters.replace_outliers(u, v, method=filter_method, max_iter=max_filter_iteration, kernel_size=filter_kernel_size) if do_sig2noise == True and current_iteration == iterations and iterations != 1: sig2noise_ratio = sig2noise_ratio_function( correlation, sig2noise_method=sig2noise_method, width=sig2noise_mask) sig2noise_ratio = sig2noise_ratio.reshape(shapes) else: sig2noise_ratio = np.full_like(u, np.nan) return x, y, u, v, sig2noise_ratio, mask
def first_pass(frame_a, frame_b, window_size, overlap, iterations, correlation_method='circular', subpixel_method='gaussian', do_sig2noise=False, sig2noise_method='peak2peak', sig2noise_mask=2): """ First pass of the PIV evaluation. This function does the PIV evaluation of the first pass. It returns the coordinates of the interrogation window centres, the displacment u and v for each interrogation window as well as the mask which indicates wether the displacement vector was interpolated or not. Parameters ---------- frame_a : 2d np.ndarray the first image frame_b : 2d np.ndarray the second image window_size : int the size of the interrogation window overlap : int the overlap of the interrogation window normal for example window_size/2 subpixel_method: string the method used for the subpixel interpolation. one of the following methods to estimate subpixel location of the peak: 'centroid' [replaces default if correlation map is negative], 'gaussian' [default if correlation map is positive], 'parabolic' Returns ------- x : 2d np.array array containg the x coordinates of the interrogation window centres y : 2d np.array array containg the y coordinates of the interrogation window centres u : 2d np.array array containing the u displacement for every interrogation window u : 2d np.array array containing the u displacement for every interrogation window """ cor_win_1 = pyprocess.moving_window_array(frame_a, window_size, overlap) cor_win_2 = pyprocess.moving_window_array(frame_b, window_size, overlap) '''Filling the interrogation window. They windows are arranged in a 3d array with number of interrogation window *window_size*window_size this way is much faster then using a loop''' correlation = correlation_func(cor_win_1, cor_win_2, correlation_method=correlation_method, normalized_correlation=False) 'do the correlation' disp = np.zeros((np.size(correlation, 0), 2)) #create a dummy for the loop to fill for i in range(0, np.size(correlation, 0)): ''' determine the displacment on subpixel level ''' disp[i, :] = find_subpixel_peak_position( correlation[i, :, :], subpixel_method=subpixel_method) 'this loop is doing the displacment evaluation for each window ' disp = np.array(disp) - np.floor(np.array(correlation[0, :, :].shape) / 2) shapes = np.array( pyprocess.get_field_shape(frame_a.shape, window_size, overlap)) u = disp[:, 1].reshape(shapes) v = -disp[:, 0].reshape(shapes) 'reshaping the interrogation window to vector field shape' x, y = get_coordinates(frame_a.shape, window_size, overlap) 'get coordinates for to map the displacement' if do_sig2noise == True and iterations == 1: sig2noise_ratio = sig2noise_ratio_function( correlation, sig2noise_method=sig2noise_method, width=sig2noise_mask) sig2noise_ratio = sig2noise_ratio.reshape(shapes) else: sig2noise_ratio = np.full_like(u, np.nan) return x, y, u, v, sig2noise_ratio
def extended_search_area_piv3D( frame_a, frame_b, window_size, overlap=(0, 0, 0), dt=(1.0, 1.0, 1.0), search_area_size=None, correlation_method="fft", subpixel_method="gaussian", sig2noise_method=None, width=2, nfftx=None, nffty=None, nfftz=None, ): """Standard PIV cross-correlation algorithm, with an option for extended area search that increased dynamic range. The search region in the second frame is larger than the interrogation window size in the first frame. This is a pure python implementation of the standard PIV cross-correlation algorithm. It is a zero order displacement predictor, and no iterative process is performed. Parameters ---------- frame_a : 3d np.ndarray an two dimensions array of integers containing grey levels of the first frame. frame_b : 3d np.ndarray an two dimensions array of integers containing grey levels of the second frame. window_size : tuple the size of the (square) interrogation window, [default: 32 pix]. overlap : tuple the number of pixels by which two adjacent windows overlap [default: 16 pix]. dt : tuple the time delay separating the two frames [default: 1.0]. correlation_method : string only one method is currently implemented: 'fft' subpixel_method : string one of the following methods to estimate subpixel location of the peak: 'centroid' [replaces default if correlation map is negative], 'gaussian' [default if correlation map is positive], 'parabolic'. sig2noise_method : string defines the method of signal-to-noise-ratio measure, ('peak2peak' or 'peak2mean'. If None, no measure is performed.) nfftx : int the size of the 3D FFT in x-direction, [default: 2 x windows_a.shape[0] is recommended] nffty : int the size of the 3D FFT in y-direction, [default: 2 x windows_a.shape[1] is recommended] nfftz : int the size of the 3D FFT in z-direction, [default: 2 x windows_a.shape[2] is recommended] width : int the half size of the region around the first correlation peak to ignore for finding the second peak. [default: 2]. Only used if ``sig2noise_method==peak2peak``. search_area_size : tuple the size of the interrogation window in the second frame, default is the same interrogation window size and it is a fallback to the simplest FFT based PIV Returns ------- u : 3d np.ndarray a three dimensional array containing the u velocity component, in pixels/seconds. v : 3d np.ndarray a three dimensional array containing the v velocity component, in pixels/seconds. w : 3d np.ndarray a three dimensional array containing the w velocity component, in pixels/seconds. sig2noise : 3d np.ndarray, (optional: only if sig2noise_method is not None) a three dimensional array the signal to noise ratio for each window pair. """ # checking if the input is correct window_size, overlap, search_area_size = check_input( window_size, overlap, search_area_size, frame_a, frame_b) # get field shape field_shape = get_field_shape(frame_a.shape, search_area_size, overlap) u = np.zeros(field_shape) v = np.zeros(field_shape) w = np.zeros(field_shape) # if we want sig2noise information, allocate memory if sig2noise_method is not None: sig2noise = np.zeros(field_shape) # shift for x and y coordinates of the search area windows so that the centers of search area windows have # the same distances to the image edge at all sides. For simplicity only shifts by integers are allowed x_centering = (frame_a.shape[1] - 1 - ((field_shape[1] - 1) * (window_size[1] - overlap[1]) + (search_area_size[1] - 1))) // 2 y_centering = (frame_a.shape[0] - 1 - ((field_shape[0] - 1) * (window_size[0] - overlap[0]) + (search_area_size[0] - 1))) // 2 z_centering = (frame_a.shape[2] - 1 - ((field_shape[2] - 1) * (window_size[2] - overlap[2]) + (search_area_size[2] - 1))) // 2 # loop over the interrogation windows # i, j are the row, column indices of the center of each interrogation # window for k in tqdm(range(field_shape[0])): for m in range(field_shape[1]): for l in range(field_shape[2]): # centers of search area. (window_size - overlap) defines the distance between each center # and (search_area_size - 1)/2.0 moves the center points away from the left or top image edge y = k * (window_size[0] - overlap[0]) + (search_area_size[0] - 1) / 2.0 x = m * (window_size[1] - overlap[1]) + (search_area_size[1] - 1) / 2.0 z = l * (window_size[2] - overlap[2]) + (search_area_size[2] - 1) / 2.0 # moving the coordinates a bit to the center, to guarantee that the distance of a extreme # point at the image edges is symmetric all all edges x += x_centering y += y_centering z += z_centering # left, right, top, bottom, front, back indices of the search area edges # note that x - (search_area_size +/- 1)/2 always returns an integer due to the definition of x and y # see also "get_coordinates()" il = int(y - (search_area_size[0] - 1) / 2.0) ir = int(y + (search_area_size[0] + 1) / 2.0) it = int(x - (search_area_size[1] - 1) / 2.0) ib = int(x + (search_area_size[1] + 1) / 2.0) ifr = int(z - (search_area_size[2] - 1) / 2.0) iba = int(z + (search_area_size[2] + 1) / 2.0) # picking the search area from frame b window_b = frame_b[il:ir, it:ib, ifr:iba] # left, right, top, bottom, front, back indices of the interrogation window # Sometimes the interrogation window cannot be placed in the middle of the search area, e.g. # in the case of window_size=3 search_area_size=4. In this case the interrogation window # is shifted 0.5 pixels to the left/top, which is achieved by rounding the indices # down during the int() conversion il = int(y - (window_size[0] - 1) / 2) ir = int(y + (window_size[0] + 1) / 2) it = int(x - (window_size[1] - 1) / 2) ib = int(x + (window_size[1] + 1) / 2) ifr = int(z - (window_size[2] - 1) / 2) iba = int(z + (window_size[2] + 1) / 2) # picking the interrogation window from frame a window_a = frame_a[il:ir, it:ib, ifr:iba] if np.any(window_a): corr = correlate_windows( window_a, window_b, correlation_method=correlation_method, nfftx=nfftx, nffty=nffty, nfftz=nfftz, ) # get subpixel approximation for peak position row and column index row, col, z = find_subpixel_peak_position( corr, subpixel_method=subpixel_method) row -= (search_area_size[0] + window_size[0] - 1) // 2 col -= (search_area_size[1] + window_size[1] - 1) // 2 z -= (search_area_size[2] + window_size[2] - 1) // 2 # get displacements, apply coordinate system definition u[k, m, l], v[k, m, l], w[k, m, l] = col, -row, -z # get signal to noise ratio if sig2noise_method is not None: sig2noise[k, m, l] = sig2noise_ratio( corr, sig2noise_method=sig2noise_method, width=width) # return output if user wanted sig2noise information if sig2noise_method is not None: return u / dt[0], v / dt[1], w / dt[2], sig2noise else: return u / dt[0], v / dt[1], w / dt[2]