def detect_triple_points(self, smooth=None, use_x_minima=False, verbose=False): """ Compute the triple points (water, oil and air interfaces) positions. Parameters ========== smooth: number Smoothing factor for the triple point position. use_x_minima: boolean If True, try to define the triple point as the minimal x values and fall back to the curvature method if necessary. (default to False). Returns ======= tripl_pts: 2x2 array of numbers Position of the triple points for each edge ([pt1, pt2]) """ if verbose: pg = ProgressCounter(init_mess="Detecting triple point positions", nmb_max=len(self.fits), name_things='triple points', perc_interv=5) for fit in self.fits: try: fit.detect_triple_points(use_x_minima=use_x_minima) except Exception: pass if verbose: pg.print_progress() if smooth is not None: self.smooth_triple_points(tos='gaussian', size=smooth)
def fit_polyline(self, deg=5, iteration_hook=None, verbose=False): """ Compute a polyline fitting for the droplets shape. Parameters ---------- deg : integer Degree of the polynomial fitting. iteration_hook: function Hook run at each iterations with the iteration number and the total number of iterations planned. """ if verbose: pg = ProgressCounter(init_mess="Fitting droplet interfaces", nmb_max=len(self.point_sets), name_things='edges', perc_interv=5) fits = [] for i, edge in enumerate(self.point_sets): try: fits.append(edge.fit_polyline(deg=deg, verbose=False)) except Exception: fits.append( dropfit.DropFit(baseline=edge.baseline, x_bounds=edge.x_bounds, y_bounds=edge.y_bounds)) if verbose: pg.print_progress() if iteration_hook is not None: iteration_hook(i, len(self.point_sets)) # return tf = TemporalSplineFits(fits=fits, temporaledges=self) return tf
def fit_ellipse(self, triple_pts=None, iteration_hook=None, verbose=False): """ Fit an ellipse to the edges. Ignore the lower part of the drop if triple points are presents. Parameters ---------- iteration_hook: function Hook run at each iterations with the iteration number and the total number of iterations planned. """ if verbose: pg = ProgressCounter(init_mess="Fitting ellipses to edges", nmb_max=len(self.point_sets), name_things='edges', perc_interv=5) fits = [] for i, edge in enumerate(self.point_sets): try: fits.append(edge.fit_ellipse(triple_pts=triple_pts)) except Exception: fits.append( dropfit.DropFit(baseline=edge.baseline, x_bounds=edge.x_bounds, y_bounds=edge.y_bounds)) if verbose: pg.print_progress() if iteration_hook is not None: iteration_hook(i, len(self.point_sets)) # return tf = TemporalEllipseFits(fits=fits, temporaledges=self) return tf
def fit_circles(self, triple_pts, sigma_max=None, verbose=False, nmb_pass=1, soft_constr=False, iteration_hook=None): """ Fit circles to the edges, cutting them if a triple point is present. Parameters ========== sigma_max: number If specified, points too far from the fit are iteratively removed until: std(R) < mean(R)*sigma_max With R the radii. nmb_pass: positive integer If superior to 1, specify the number of pass to make. Addintional passes use the previously triple points detected by circle fits to more accurately detect the next ones. iteration_hook: function Hook run at each iterations with the iteration number and the total number of iterations planned. """ if verbose: pg = ProgressCounter(init_mess="Fitting circles to edges", nmb_max=len(self.point_sets) * nmb_pass, name_things='edges', perc_interv=5) # check if len(triple_pts) != len(self.point_sets): raise Exception('Not the right ner of triple points') # passes tf = None for i in range(nmb_pass): if tf is not None: tf.smooth_triple_points('gaussian', size=10) triple_pts = tf.get_triple_points() fits = [] for tp, edge in zip(triple_pts, self.point_sets): try: fits.append( edge.fit_circles(triple_pts=tp, sigma_max=sigma_max, soft_constr=soft_constr)) except Exception: fits.append( dropfit.DropFit(baseline=edge.baseline, x_bounds=edge.x_bounds, y_bounds=edge.y_bounds)) if verbose: pg.print_progress() if iteration_hook is not None: iteration_hook(i, len(self.point_sets) * nmb_pass) tf = TemporalCirclesFits(fits=fits, temporaledges=self) # return return tf
def get_background(self, noise_level=10, verbose=False): """ Return the background image based on the histogram of values at each point. Parameters ---------- noise_level: integer Level of noise used to differentiate between noise and real perturbation of the background image. Returns ------- bg: ScalarField object Background image """ # Data min_len = len(self.times) / 10 values = np.zeros(self.shape) if verbose: pg = ProgressCounter(init_mess="Computing background image", nmb_max=self.shape[0] * self.shape[1], name_things="Pixels", perc_interv=1) # Spatial loop for i, j in np.ndindex(self.shape): vals = np.array([ self.fields[n]._ScalarField__values[i, j] for n in range(len(self.fields)) ]) # # HIST # hist = np.bincount(vals) # val = np.argmax(hist) # STATS std = np.std(vals, dtype=float) old_std = 0 mean = np.mean(vals, dtype=float) while True: vals = vals[np.logical_and(vals > mean - std, vals < mean + std)] old_std = std std = np.std(vals) mean = np.mean(vals) if std < noise_level or len(vals) <= min_len or old_std == std: break values[i, j] = mean if verbose: pg.print_progress() bg = sf.ScalarField() bg.import_from_arrays(axe_x=self.axe_x, axe_y=self.axe_y, values=values, unit_x=self.unit_x, unit_y=self.unit_y) return bg
def substract_background(self, bg, noise_level=10, blend=True, filling_value=None, verbose=False): """ Remove the background without changing the perturbations. Parameters ---------- bg: ScalarField object Background image noise_level: integer Level of noise used to differentiate between noise and real perturbation of the background image. blend: boolean Blend the foreground in the background. filling_value: integer Value used to fill the background. Default to the background spatial average. Returns ------- nsf: TemporalScalarFields object substracted fields. """ # data ntf = self.copy() bg_mean = bg.mean if filling_value is None: filling_value = bg_mean if verbose: pg = ProgressCounter(init_mess="Substracting background image", nmb_max=len(self.fields), name_things="Fields") # Spatial loop for n in range(len(ntf)): # Get foreground values = ntf.fields[n].values mask = np.logical_and(values < bg.values + noise_level, bg.values - noise_level < values) # get foreground and background avg if blend: fg_mean = np.mean(values[~mask]) blend_field = (values - bg_mean) / (fg_mean - bg_mean) blend_field = ndimage.gaussian_filter(blend_field, 1) values = values * blend_field + (1 - blend_field) * filling_value values[mask] = filling_value ntf.fields[n].values = values if verbose: pg.print_progress() # return ntf
def edge_detection_contour(self, size_ratio=.5, nmb_edges=2, level=0.5, ignored_pixels=2, iteration_hook=None, verbose=False): """ Perform edge detection. Parameters ========== nmb_edges: integer Number of maximum expected edges (default to 2). level: number Normalized level of the drop contour. Should be between 0 (black) and 1 (white). Default to 0.5. size_ratio: number Minimum size of edges, regarding the bigger detected one (default to 0.5). ignored_pixels: integer Number of pixels ignored around the baseline (default to 2). Putting a small value to this allow to avoid small surface defects to be taken into account. """ pts = tde.TemporalDropEdges() pts.baseline = self.baseline if verbose: pg = ProgressCounter("Detecting drop edges", "Done", len(self.fields), 'images', 5) for i in range(len(self.fields)): try: pt = self.fields[i].edge_detection_contour( nmb_edges=nmb_edges, level=level, ignored_pixels=ignored_pixels, size_ratio=size_ratio) except Exception: pt = dropedges.DropEdges(xy=[], im=self, type='contour') pts.add_pts(pt, time=self.times[i], unit_times=self.unit_times) if verbose: pg.print_progress() if iteration_hook is not None: iteration_hook(i, len(self.fields)) return pts
def compute_contact_angle(self, iteration_hook=None, verbose=False): """ Compute the drop contact angles. """ if verbose: pg = ProgressCounter(init_mess="Getting contact angles", nmb_max=len(self.fits), name_things='images', perc_interv=5) for i, fit in enumerate(self.fits): try: fit.compute_contact_angle() except Exception: pass if verbose: pg.print_progress() if iteration_hook is not None: iteration_hook(i, len(self.fits))
def fit_spline(self, k=5, s=0.75, iteration_hook=None, verbose=False): """ Compute a spline fitting for the droplets shape. Parameters ---------- k : int, optional Degree of the smoothing spline. Must be <= 5. Default is k=5. s : float, optional Smoothing factor between 0 (not smoothed) and 1 (very smoothed) Default to 0.75 iteration_hook: function Hook run at each iterations with the iteration number and the total number of iterations planned. """ if verbose: pg = ProgressCounter(init_mess="Fitting droplet interfaces", nmb_max=len(self.point_sets), name_things='edges', perc_interv=5) fits = [] for i, edge in enumerate(self.point_sets): try: fits.append(edge.fit_spline(k=k, s=s, verbose=False)) except Exception: fits.append( dropfit.DropFit(baseline=edge.baseline, x_bounds=edge.x_bounds, y_bounds=edge.y_bounds)) if verbose: pg.print_progress() if iteration_hook is not None: iteration_hook(i, len(self.point_sets)) # return tf = TemporalSplineFits(fits=fits, temporaledges=self) return tf
def get_phase_map(self, freq, tf=None, check_spec=None, verbose=True): """ Return the phase map of the temporal scalar field for the given frequency. Parameters ---------- freq: number Wanted frequency tf: Integer Last time indice to use check_spec: Integer If not None, specify the number of spectrum to display (useful to check if choosen frequencies are relevant). verbose: Boolean . Returns ------- phase_map: ScalarField object . """ # phases = np.zeros(self.shape, dtype=float) norms = np.zeros(self.shape, dtype=float) compo = "values" if tf is None: tf = len(self.fields) # select spectrum to display if check_spec is not None: check_spec_inds = np.random.choice( range(self.shape[0] * self.shape[1]), check_spec) # get phases if verbose: pg = ProgressCounter(init_mess="Computing phase maps", nmb_max=self.shape[0] * self.shape[1], name_things="profiles") for i, x in enumerate(self.axe_x): for j, y in enumerate(self.axe_y): if verbose: pg.print_progress() # get profile profile = self.get_time_profile(compo, (x, y), wanted_times=[0, tf]) prof = profile.y dx = profile.x[1] - profile.x[0] # get fft fft = np.fft.fft(prof) fft = fft[0:int(len(fft) / 2)] fft_norm = np.abs(fft) fft_phase = np.angle(fft) fft_f = np.fft.fftfreq(len(prof), dx)[0:len(fft)] # get phase at the wanted frequency ind = np.argmin(abs(fft_f - freq)) # store phases and norms phases[i, j] = fft_phase[ind] norms[i, j] = fft_norm[ind] # display spectrum if asked if check_spec: if i * len(self.axe_x) + j in check_spec_inds: fig = plt.figure() ax = fig.add_subplot(1, 1, 1) ax.loglog(fft_f, fft_norm) ax2 = ax.twinx() ax2.plot([], []) ax2.semilogx(fft_f, fft_phase) plt.axvline(fft_f[ind], color="k", ls="--") plt.show(block=True) # return norm = sf.ScalarField() norm.import_from_arrays(axe_x=self.axe_x, axe_y=self.axe_y, values=norms.transpose(), unit_x=self.unit_x, unit_y=self.unit_y, unit_values="") phase = sf.ScalarField() phase.import_from_arrays(axe_x=self.axe_x, axe_y=self.axe_y, values=phases.transpose(), unit_x=self.unit_x, unit_y=self.unit_y, unit_values="rad") return norm, phase
def edge_detection_canny(self, threshold1=None, threshold2=None, base_max_dist=30, size_ratio=.5, ignored_pixels=2, nmb_edges=2, keep_exterior=True, dilatation_steps=1, smooth_size=None, iteration_hook=None, verbose=False): """ Perform edge detection. Parameters ========== threshold1, threshold2: integers Thresholds for the Canny edge detection method. (By default, inferred from the data histogram) base_max_dist: integers Maximal distance (in pixel) between the baseline and the beginning of the drop edge (default to 30). nmb_edges: integer Number of maximum expected edges (default to 2). size_ratio: number Minimum size of edges, regarding the bigger detected one (default to 0.5). keep_exterior: boolean If True (default), only keep the exterior edges. smooth_size: number If specified, the image is smoothed before performing the edge detection. (can be useful to put this to 1 to get rid of compression artefacts on images). dilatation_steps: positive integer Number of dilatation/erosion steps. Increase this if the drop edges are discontinuous. iteration_hook: function Hook run at each iterations with the iteration number and the total number of iterations planned. """ # check if self.baseline is None: raise Exception('You have to define a baseline first.') # all_edge_empty = True pts = tde.TemporalDropEdges() pts.baseline = self.baseline if verbose: pg = ProgressCounter(init_mess="Detecting drop edges", nmb_max=len(self.fields), name_things='images', perc_interv=5) for i in range(len(self.fields)): try: pt = self.fields[i].edge_detection_canny( threshold1=threshold1, threshold2=threshold2, base_max_dist=base_max_dist, size_ratio=size_ratio, ignored_pixels=ignored_pixels, keep_exterior=keep_exterior, nmb_edges=nmb_edges, dilatation_steps=dilatation_steps, smooth_size=smooth_size) all_edge_empty = False except Exception: pt = dropedges.DropEdges(xy=[], im=self, type='canny') pts.add_pts(pt, time=self.times[i], unit_times=self.unit_times) if iteration_hook is not None: iteration_hook(i, len(self.fields)) if verbose: pg.print_progress() # check if edges has been detected if all_edge_empty: raise Exception('No edges could be detected. You should' ' check the baseline position.') return pts