Exemplo n.º 1
0
    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]]
Exemplo n.º 2
0
    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]]]
Exemplo n.º 3
0
 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
Exemplo n.º 4
0
 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()
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
 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)
Exemplo n.º 8
0
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