def smooth_contact_angle(self, tos='gaussian', size=None): """ Smooth the contact angles. Parameters ========== tos : string, optional Type of smoothing, can be 'uniform' (default) or 'gaussian' (See ndimage module documentation for more details) size : number, optional Size of the smoothing (is radius for 'uniform' and sigma for 'gaussian'). Default is 3 for 'uniform' and 1 for 'gaussian'. """ # Get the contact angles as profiles theta1 = [] theta2 = [] theta3 = [] theta4 = [] t = [] mask = [] mask2 = [] for i, fit in enumerate(self.fits): t.append(self.times[i]) if fit.thetas is not None: theta1.append(fit.thetas[0]) theta2.append(fit.thetas[1]) mask.append(False) else: theta1.append(np.nan) theta2.append(np.nan) mask.append(True) if fit.thetas_triple is not None: theta3.append(fit.thetas_triple[0]) theta4.append(fit.thetas_triple[1]) mask2.append(False) else: theta3.append(np.nan) theta4.append(np.nan) mask2.append(True) # smooth if not np.all(mask): theta1 = Profile(t, theta1).smooth(tos='gaussian', size=size).y theta2 = Profile(t, theta2).smooth(tos='gaussian', size=size).y for i, fit in enumerate(self.fits): if not mask[i]: fit.thetas = [theta1[i], theta2[i]] if not np.all(mask2): theta3 = Profile(t, theta3).smooth(tos='gaussian', size=size).y theta4 = Profile(t, theta4).smooth(tos='gaussian', size=size).y for i, fit in enumerate(self.fits): if not mask2[i]: fit.thetas_triple = [theta3[i], theta4[i]]
def smooth_triple_points(self, tos='gaussian', size=None): """ Smooth the position of the triple point. Parameters ========== tos : string, optional Type of smoothing, can be 'uniform' (default) or 'gaussian' (See ndimage module documentation for more details) size : number, optional Size of the smoothing (is radius for 'uniform' and sigma for 'gaussian'). Default is 3 for 'uniform' and 1 for 'gaussian'. """ # Get triple points as profiles x1 = [] y1 = [] x2 = [] y2 = [] t = [] mask = [] for i, fit in enumerate(self.fits): t.append(self.times[i]) if fit.triple_pts is not None: x1.append(fit.triple_pts[0][0]) x2.append(fit.triple_pts[1][0]) y1.append(fit.triple_pts[0][1]) y2.append(fit.triple_pts[1][1]) mask.append(False) else: x1.append(np.nan) x2.append(np.nan) y1.append(np.nan) y2.append(np.nan) mask.append(True) # Smooth the profiles if not np.all(np.isnan(x1)): x1 = Profile(t, x1).smooth(tos='gaussian', size=size).y x2 = Profile(t, x2).smooth(tos='gaussian', size=size).y y1 = Profile(t, y1).smooth(tos='gaussian', size=size).y y2 = Profile(t, y2).smooth(tos='gaussian', size=size).y # Replace by the smoothed version for i, fit in enumerate(self.fits): if not mask[i]: fit.triple_pts = [[x1[i], y1[i]], [x2[i], y2[i]]]
def compute_velocity_on_line(self, xya, xyb, res=100, raw=False, remove_solid=False): """ Return the velocity on the given line. """ # update sigma if necessary if self.sigma_to_refresh: self.solving_sigma() # creating the line xa, ya = xya xb, yb = xyb x = np.linspace(xa, xb, res) y = np.linspace(ya, yb, res) vx = np.empty(x.shape) vy = np.empty(x.shape) # mask if we don't want velocities on solid mask = np.zeros(x.shape) paths = self.get_solid_paths() # loop on line for i, _ in enumerate(x): if remove_solid: in_solid = np.any( [path.contains_point((x[i], y[i])) for path in paths]) if in_solid: mask[i] = True continue v = self.compute_velocity(x[i], y[i]) vx[i] = v[0] vy[i] = v[1] # returning if raw: return vx, vy else: x = x - np.min(x) y = y - np.min(y) new_x = np.sqrt(x**2 + y**2) - np.sqrt(x[0]**2 + y[0]**2) prof_vx = Profile(new_x, vx, mask=mask, unit_x='m', unit_y='m/s') prof_vy = Profile(new_x, vy, mask=mask, unit_x='m', unit_y='m/s') return prof_vx, prof_vy
def compute_pressure_from_velocity(self, obj, raw=False): """ Return the pressure coefficient computed from velocity data. """ if isinstance(obj, VectorField): vx = obj.comp_x vy = obj.comp_y grid_x = obj.axe_x grid_y = obj.axe_y mask = obj.mask cp = 1. - (vx**2 + vy**2) / self.u_inf**2 mask = np.logical_or(np.isnan(cp), mask) if raw: return cp else: sf = ScalarField() sf.import_from_arrays(grid_x, grid_y, cp, mask=mask, unit_x='m', unit_y='m', unit_values='') return sf elif isinstance(obj, (tuple, list)): if isinstance(obj[0], Profile): vx = obj[0].y vy = obj[1].y mask = np.logical_or(obj[0].mask, obj[1].mask) cp = 1. - (vx**2 + vy**2) / self.u_inf**2 if raw: return cp else: prof = Profile(obj[0].x, cp, mask=mask, unit_x=obj[0].unit_x, unit_y='') return prof else: raise TypeError() else: raise TypeError()
def edge_detection_canny(self, threshold1=None, threshold2=None, base_max_dist=15, size_ratio=.5, nmb_edges=2, ignored_pixels=2, keep_exterior=True, remove_included=True, smooth_size=None, dilatation_steps=1, verbose=False, debug=False): """ Perform edge detection using canny 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 15). size_ratio: number Minimum size of edges, regarding the bigger detected one (default to 0.5). nmb_edges: integer Number of maximum expected edges (default to 2). 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. keep_exterior: boolean If True (default), only keep the exterior edges. remove_included: boolean If True (default), remove edges included in other 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. """ # check for baseline if self.baseline is None: raise Exception('You should set the baseline first.') if not np.isclose(self.dx, self.dy): warnings.warn('dx is different than dy, results can be weird...') # Get adapted thresholds if threshold2 is None or threshold1 is None: # hist = self.get_histogram(cum=True, # bins=int((self.max - self.min)/10)) mini = 0 maxi = 255 hist = cv2.calcHist([self.values], [0], None, [maxi - mini], [mini, maxi]) hist = np.cumsum(hist[:, 0]) hist = Profile(np.arange(mini, maxi), hist) threshold1 = hist.get_value_position(hist.min + (hist.max - hist.min) / 2)[0] threshold2 = np.max(hist.x) * .5 # Remove useless part of the image tmp_im = self.crop(intervy=[ np.min([self.baseline.pt2[1], self.baseline.pt1[1]]), np.inf ], inplace=False) # Smooth if asked if smooth_size is not None: if smooth_size != 0: tmp_im.smooth(tos='gaussian', size=smooth_size, inplace=True) if verbose: plt.figure() tmp_im.display() plt.title('Initial image') #====================================================================== # Perform Canny detection #====================================================================== im = np.array(tmp_im.values, dtype=np.uint8) im_edges = cv2.Canny(image=im, threshold1=threshold1, threshold2=threshold2) if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Canny edge detection \nwith th1={} and th2={}'.format( threshold1, threshold2)) #====================================================================== # Remove points behind the baseline (and too close to) #====================================================================== fun = self.baseline.get_baseline_fun() ign_dy = ignored_pixels * self.dy max_y = np.max( [self.baseline.pt2[1] + ign_dy, self.baseline.pt1[1] + ign_dy]) for j in range(im_edges.shape[1]): y = tmp_im.axe_y[j] if y > max_y: continue for i in range(im_edges.shape[0]): x = tmp_im.axe_x[i] if y < fun(x) + ign_dy: im_edges[i, j] = 0 if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Removed points under baseline') #====================================================================== # Dilatation / erosion to ensure line continuity #====================================================================== if dilatation_steps > 0: im_edges = cv2.dilate(im_edges, iterations=dilatation_steps, kernel=np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]])) # im_edges = spim.binary_erosion(im_edges, iterations=dilatation_steps, # structure=[[0, 1, 0], [1, 1, 1], [0, 1, 0]]) if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Dilatation / erosion step') #====================================================================== # Keep only the bigger edges #====================================================================== nmb, labels = cv2.connectedComponents(im_edges) # safeguard if nmb > 1000: raise Exception("Too many edges detected, you may want to use the " "'smooth' parameter") # labels, nmb = spim.label(im_edges, np.ones((3, 3))) nmb_edge = nmb if debug: print(f" Initial number of edges: {nmb_edge}") dy = self.axe_y[1] - self.axe_y[0] if nmb_edge > 1: #================================================================== # Remove small patches #================================================================== sizes = [ np.sum(labels == label) for label in np.arange(1, nmb + 1) ] crit_size = np.sort(sizes)[-1] * size_ratio for i, size in enumerate(sizes): if size < crit_size: im_edges[labels == i + 1] = 0 labels[labels == i + 1] = 0 nmb_edge -= 1 if debug: print(f"Label {i+1} removed because too small") print(f" Remaining edges: {nmb_edge}") if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Removed small edges') #================================================================== # Remove lines not touching the baseline #================================================================== if nmb_edge > nmb_edges: for i in range(np.max(labels)): # Untested ! (next line) ys = tmp_im.axe_y[np.sum(labels == i + 1, axis=0) > 0] if len(ys) == 0: continue min_y = np.min(ys) mdist = base_max_dist * dy if min_y > mdist + np.max( [self.baseline.pt1[1], self.baseline.pt2[1]]): if debug: print(f"Label {i+1} removed because not touching " "baseline") print(f" Remaining edges: {nmb_edge}") im_edges[labels == i + 1] = 0 labels[labels == i + 1] = 0 nmb_edge -= 1 if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Removed edge not touching the baseline') #============================================================================== # Remove edges that are included in another edge #============================================================================== if nmb_edge > 1 and remove_included: maxs = [] mins = [] # Get upper and lower bounds for edges for i in np.arange(1, nmb + 1): xs = tmp_im.axe_x[np.sum(labels == i, axis=1) > 0] if len(xs) == 0: mins.append(None) maxs.append(None) else: mins.append(np.min(xs)) maxs.append(np.max(xs)) # Remove edge if included in another one for i in range(nmb): if mins[i] is None: continue for j in range(nmb): if mins[j] is None: continue if mins[i] < mins[j] and maxs[j] < maxs[i]: if debug: print(f"Label {j+1} removed because" " included in another one") print(f" Remaining edges: {nmb_edge}") im_edges[labels == j + 1] = 0 labels[labels == j + 1] = 0 nmb_edge -= 1 if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Removed edge included in another edge') #================================================================== # Keep only the exterior edges #================================================================== if nmb_edge > 2 and keep_exterior: mean_xs = [] for i in np.arange(1, nmb + 1): xs = tmp_im.axe_x[np.sum(labels == i, axis=1) > 0] if len(xs) == 0: mean_xs.append(np.nan) else: mean_xs.append(np.mean(xs)) mean_xs = np.asarray(mean_xs) # mean_xs[np.isnan(mean_xs)] = np.mean(mean_xs[~np.isnan(mean_xs)]) mean_xs_indsort = np.argsort(mean_xs) for i in mean_xs_indsort[1:-1]: if np.isnan(mean_xs[i + 1]): break if debug: print(f"Label {i+1} removed because not exterior") print(f" Remaining edges: {nmb_edge}") im_edges[labels == i + 1] = 0 labels[labels == i + 1] = 0 nmb_edge -= 1 if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Removed the interior edges') #============================================================================== # Let only the maximum allowed number of edges #============================================================================== if nmb_edge > nmb_edges and keep_exterior: sizes = [ np.sum(labels == label) for label in np.arange(1, nmb + 1) ] indsort = np.argsort(sizes) for ind in indsort[:-nmb_edges]: im_edges[ind + 1 == labels] = 0 nmb_edge = nmb_edges if verbose: plt.figure() im = Image() im.import_from_arrays(tmp_im.axe_x, tmp_im.axe_y, im_edges, mask=tmp_im.mask, unit_x=tmp_im.unit_x, unit_y=tmp_im.unit_y) im.display() plt.title('Removed small edges because too numerous') #====================================================================== # Check and Return #====================================================================== # Get points coordinates xs, ys = np.where(im_edges) axx = tmp_im.axe_x axy = tmp_im.axe_y xs = [axx[x] for x in xs] ys = [axy[y] for y in ys] xys = list(zip(xs, ys)) # Check if there is something remaining if len(xys) < 10: if verbose: plt.show(block=False) raise Exception("Didn't found any drop here...") if verbose: plt.figure() self.display() xys = np.array(xys) plt.plot(xys[:, 0], xys[:, 1], ".k") plt.title('Initial image + edge points') edge = DropEdges(xy=xys, im=self, type="canny") return edge
def minfun(gamma): return thetap - K1 - np.abs(delta_rho * g * z[1::] / gamma) gamma, res = spopt.leastsq(minfun, (gamma_0, )) if res > 4: raise Exception(res) return gamma s = np.linspace(0, 1, 100) x = s z = 1 - (1 - s**2)**.5 dists = ((x[1::] - x[0:-1])**2 + (z[1::] - z[0:-1])**2)**.5 cumdists = np.cumsum(np.concatenate(([0], dists))) s = cumdists / cumdists[-1] x = Profile(s, x) x.evenly_space(inplace=True) z = Profile(s, z) z.evenly_space(inplace=True) s = z.x z = z.y x = x.y plt.figure() plt.plot(s, x, 'o') plt.figure() plt.plot(s, z, 'o') plt.figure() plt.plot(x, z, 'o') gamma = fit_YL(s, x, z, 1000 - 1.25)
def _separate_drop_edges(self): """ Separate the two sides of the drop. """ # check if edges are present... if len(self.xy) == 0: self.drop_edges = [None]*4 return [None]*4 # Get cut position ind_sort = np.argsort(self.xy[:, 0]) xs = self.xy[:, 0][ind_sort] ys = self.xy[:, 1][ind_sort] dxs = xs[1::] - xs[0:-1] dxs_sorted = np.sort(dxs) if dxs_sorted[-1] > 10*dxs_sorted[-2]: # ind of the needle center (needle) ind_cut = np.argmax(dxs) + 1 x_cut = xs[ind_cut] else: # ind of the higher point (no needle) ind_cut = np.argmax(ys) x_cut = xs[ind_cut] # Separate according to cut position xs1 = xs[xs < x_cut] xs2 = xs[xs > x_cut] ys1 = ys[xs < x_cut] ys2 = ys[xs > x_cut] # # Ensure only one y per x (Use hash table for optimization) new_y1 = np.sort(list(set(ys1))) dic = {y1: 0 for y1 in new_y1} nmb = {y1: 0 for y1 in new_y1} for i in range(len(ys1)): dic[ys1[i]] += xs1[i] nmb[ys1[i]] += 1 new_x1 = [dic[y1]/nmb[y1] for y1 in new_y1] new_y2 = np.sort(list(set(ys2))) dic = {y2: 0 for y2 in new_y2} nmb = {y2: 0 for y2 in new_y2} for i in range(len(ys2)): dic[ys2[i]] += xs2[i] nmb[ys2[i]] += 1 new_x2 = [dic[y2]/nmb[y2] for y2 in new_y2] # smooth to avoid stepping # Profile also automatically sort along y de1 = Profile(new_y1, new_x1) de2 = Profile(new_y2, new_x2) de1.smooth(tos='gaussian', size=1, inplace=True) de2.smooth(tos='gaussian', size=1, inplace=True) # parametrize if len(de1) != 0: t1s = np.cumsum(((de1.x[1:] - de1.x[0:-1])**2 + (de1.y[1:] - de1.y[0:-1])**2)**.5) t1s = np.concatenate(([0], t1s)) t1s /= t1s[-1] else: t1s = [] if len(de2) != 0: t2s = np.cumsum(((de2.x[1:] - de2.x[0:-1])**2 + (de2.y[1:] - de2.y[0:-1])**2)**.5) t2s = np.concatenate(([0], t2s)) t2s /= t2s[-1] else: t2s = [] dex1 = Profile(t1s, de1.y, unit_x="", unit_y=self.unit_x) dex2 = Profile(t2s, de2.y, unit_x="", unit_y=self.unit_x) dey1 = Profile(t1s, de1.x, unit_x="", unit_y=self.unit_y) dey2 = Profile(t2s, de2.x, unit_x="", unit_y=self.unit_y) # evenlify if len(dex1) != 0: dtx1 = self.im_dx/abs(dex1.y[-1] - dex1.y[0]) dty1 = self.im_dy/abs(dey1.y[-1] - dey1.y[0]) dt1 = np.min([dtx1, dty1]) dex1 = dex1.evenly_space(dx=dt1) dey1 = dey1.evenly_space(dx=dt1) if len(dex2) != 0: dtx2 = self.im_dx/abs(dex2.y[-1] - dex2.y[0]) dty2 = self.im_dy/abs(dey2.y[-1] - dey2.y[0]) dt2 = np.min([dtx2, dty2]) dex2 = dex2.evenly_space(dx=dt2) dey2 = dey2.evenly_space(dx=dt2) # store self.drop_edges = (dex1, dey1, dex2, dey2) return (dex1, dey1, dex2, dey2)
def get_separation_position(D=1., Vd=1., x_obst=10., theta_max=np.inf, nu=1e-6, res_pot=20, res_int=500): """ Use potential flow to get velocity distribution and Thwaites BL equation to get the separation position. Parameters ---------- D : number Obstacle diameter [m]. Vd : number Bulk velocity [m/s]. x_obst : number Distance from CL birth to obstacle [m]. theta_max : number Maximum value of BL momentum thickness (in case of confinement) [m]. nu : number Kinematic viscosity []. res_pot : integer Resolution for potential flow model (default=20). res_int : integer Resolution for Thwaites integral resolution (default=500). Returns ------- x_sep : number Separation position """ # creating system syst = System(u_inf=Vd, alpha=0.) syst.add_object(2, [[-D / 2., -D / 2.], [-D / 2., D / 2.], [D / 2., D / 2.], [D / 2., -D / 2.]], kind='wall', res=res_pot) syst.objects_2D[0].inverse_normals() syst.solving_sigma() # getting profiles along symmetry plane dx = x_obst x_f = -D / 2. x_0 = x_f - dx prof_vit = syst.compute_velocity_on_line([x_f, 0], [x_0, 0], res=res_int, remove_solid=True)[0] # compute theta**2 U = prof_vit.y theta_0 = 0. x_0 = -dx x_f = 0. x = np.linspace(x_0, x_f, res_int) U5 = prof_vit.y**5 integral_U = [ np.trapz(U5[0:i], prof_vit.x[0:i]) for i in np.arange(len(prof_vit.x)) ] theta2 = theta_0**2 * (U[0] / U)**6 + 0.45 * nu / U**6 * integral_U theta2[theta2 > theta_max**2] = theta_max**2 # compute m deriv_U = np.gradient(U, x[1] - x[0]) m = -theta2 / nu * deriv_U # find separation m = Profile(x, m, unit_x='m', unit_y='') x_sep = m.get_value_position(0.09)[0] return -x_sep