Example #1
0
 def test_plane_fit01(self):
     """
     Tests the parameters of the plane obtained through the regression.
     """
     pnt, par = utils.plane_fit(self.points)
     numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-5, atol=0)
     self.assertAlmostEqual(self.c[-1], -sum(par*pnt))
Example #2
0
 def test_plane_fit01(self):
     """
     Tests the parameters of the plane obtained through the regression.
     """
     pnt, par = utils.plane_fit(self.points)
     numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-5, atol=0)
     self.assertAlmostEqual(self.c[-1], -sum(par * pnt))
Example #3
0
def get_points_on_plane(edges, poi):
    """
    :param edges:
    :param points:
    """

    #
    # creating a matrix of points
    pnts = []
    for edge in edges:
        pnts += [[pnt.longitude, pnt.latitude, pnt.depth]
                 for pnt in edge.points]
    pnts = np.array(pnts)
    #
    # projecting the points
    p = Proj(proj='lcc', lon_0=np.mean(pnts[:, 0]), lat_2=45)
    x, y = p(pnts[:, 0], pnts[:, 1])
    x = x / 1e3  # m -> km
    y = y / 1e3  # m -> km
    #
    # fit the plane
    tmp = np.vstack((x.flatten(), y.flatten(), pnts[:, 2].flatten())).T
    pnt, ppar = plane_fit(tmp)
    # z = -(a * x + b * y - d) / c
    d = -np.sum(pnt * ppar)
    xp, yp = p(poi[:, 0], poi[:, 1])
    poi[:, 2] = -(ppar[0] * xp / 1e3 + ppar[1] * yp / 1e3 + d) / ppar[2]
    #
    return poi
Example #4
0
 def test_plane_fit02(self):
     """
     Tests the parameters of the plane obtained through the regression.
     In this second case we add noise to the z values
     """
     self.points[:, 2] += numpy.random.random(self.npts) * 0.01
     pnt, par = utils.plane_fit(self.points)
     numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-3, atol=0)
     self.assertAlmostEqual(self.c[-1], -sum(par * pnt), 2)
Example #5
0
 def test_plane_fit02(self):
     """
     Tests the parameters of the plane obtained through the regression.
     In this second case we add noise to the z values
     """
     self.points[:, 2] += numpy.random.random(self.npts) * 0.01
     pnt, par = utils.plane_fit(self.points)
     numpy.testing.assert_allclose(self.c[0:3], par, rtol=1e-3, atol=0)
     self.assertAlmostEqual(self.c[-1], -sum(par*pnt), 2)
Example #6
0
def create_lower_surface_mesh_old(mesh, slab_thickness):
    """
    This method for the construction of the boottom surface of the slab finds
    the plane fitting the surface and the projects the top surface toward a
    direction perpendicular to the plane.

    NB don't forget the surface_to_mesh method in openquake.hazardlib.geo.mesh

    :parameter mesh:
        An instance of the :class:`openquake.hazardlib.geo.mesh.Mesh`
        describing the top of the slab within which we admit inslab seismicity
    :parameter float slab_thickness:
        Thickness of the slab [km]
    :returns:
        An instance of :class:`openquake.hazardlib.geo.mesh.Mesh`
    """
    oshape = mesh.lons.shape
    #
    # create a 3xn array with the points composing the mesh
    lld = np.array([
        mesh.lons.flatten('F'),
        mesh.lats.flatten('F'),
        mesh.depths.flatten('F')
    ]).T
    #
    # project the points using Lambert Conic Conformal - for the reference
    # meridian 'lon_0' we use the mean longitude of the grid
    p = Proj(proj='lcc', lon_0=np.mean(lld[:, 0]), lat_2=45)
    x, y = p(lld[:, 0], lld[:, 1])
    x = x / 1e3  # m -> km
    y = y / 1e3  # m -> km
    #
    # compute the equation of the plane fitting the slab surface
    xx = np.vstack((x.T, y.T, lld[:, 2])).T
    pnt, ppar = plane_fit(xx)
    #
    # compute the points on the new surface. The new surface is at a distance
    # 'slab_tickness' below the original surface in a direction perpendicular
    # to the fitted plane
    corr = 1
    if np.sign(ppar[2]) == -1:
        corr = -1
    xls = x + corr * slab_thickness * ppar[0]
    yls = y + corr * slab_thickness * ppar[1]
    zls = lld[:, 2] + corr * slab_thickness * ppar[2]
    #
    # back-projection of the points composing the lower surface
    llo, lla = p(xls * 1e3, yls * 1e3, inverse=True)
    #
    # reshape the arrays containing the geographic coordinates of the lower
    # surface
    rllo = np.reshape(llo, oshape, order='F')
    rlla = np.reshape(lla, oshape, order='F')
    rzls = np.reshape(zls, oshape, order='F')
    #
    #
    return Mesh(rllo, rlla, rzls)
Example #7
0
def _check_edges(edges):
    """
    This checks that all the edges follow the right hand rule
    :param list edges:
        The list of edges to be analysed.
    :return:
        An instance of :class:`numpy.ndarray` of cardinality equal to the
        number of edges. Where integers are positive, the edges need to be
        flipped.
    """

    # Check the input
    if len(edges) < 1:
        return None

    # Create a matrix of points
    pnts = []
    for edge in edges:
        pnts += [[pnt.longitude, pnt.latitude, pnt.depth]
                 for pnt in edge.points]
    pnts = np.array(pnts)

    # Project the points using Lambert Conic Conformal
    fmt = "+proj=lcc +lon_0={:f} +lat_1={:f} +lat_2={:f}"
    mla = np.mean(pnts[:, 1])
    srs = CRS.from_proj4(fmt.format(np.mean(pnts[:, 0]), mla - 10, mla + 10))
    p = Proj(srs)

    # From m to km
    x, y = p(pnts[:, 0], pnts[:, 1])
    x = x / 1e3  # m -> km
    y = y / 1e3  # m -> km

    # Fit the plane
    tmp = np.vstack((x.flatten(), y.flatten(), pnts[:, 2].flatten())).T
    _, ppar = plane_fit(tmp)

    # Analyse the edges
    chks = []
    for edge in edges:
        epnts = np.array([[pnt.longitude, pnt.latitude, pnt.depth]
                          for pnt in edge.points[0:2]])
        ex, ey = p(epnts[:, 0], epnts[:, 1])
        ex = ex / 1e3
        ey = ey / 1e3

        # Check the edge direction Vs plane perpendicular
        edgv = np.array([np.diff(ex[0:2])[0], np.diff(ey[0:2])[0]])
        chks.append(np.sign(np.cross(ppar[:2], edgv)))

    return np.array(chks)
Example #8
0
 def test_fit_plane_again(self):
     """
     This is a trivial test to check (again) the code we use for fitting
     the plane
     """
     a = 0.0
     b = 0.0
     c = 1
     d = -1
     expected_par = np.array([a, b, c])
     x, y = np.meshgrid(np.linspace(0, 10, 10), np.linspace(0, 10, 10))
     z = -(a * x + b * y + d) / c
     xx = np.vstack((x.flatten(), y.flatten(), z.flatten())).T
     pnt, par = plane_fit(xx)
     np.testing.assert_almost_equal(expected_par, par)
Example #9
0
def _check_edges(edges):
    """
    This checks that all the edges follow the right hand rule
    :param list edges:
        The list of edges to be analysed.
    :return:
        An instance of :class:`numpy.ndarray` of cardinality equal to the
        number of edges. Where integers are positive edges need to be flipped.
    """
    #
    # creating a matrix of points
    pnts = []
    for edge in edges:
        pnts += [[pnt.longitude, pnt.latitude, pnt.depth]
                 for pnt in edge.points]
    pnts = np.array(pnts)
    #
    # projecting the points
    p = Proj(proj='lcc', lon_0=np.mean(pnts[:, 0]), lat_1=0., lat_2=60.)
    x, y = p(pnts[:, 0], pnts[:, 1])
    x = x / 1e3  # m -> km
    y = y / 1e3  # m -> km
    #
    # fit the plane
    tmp = np.vstack((x.flatten(), y.flatten(), pnts[:, 2].flatten())).T
    _, ppar = plane_fit(tmp)
    #
    # analysing the edges
    chks = []
    for edge in edges:
        epnts = np.array([[pnt.longitude, pnt.latitude, pnt.depth] for pnt in
                          edge.points[0:2]])
        ex, ey = p(epnts[:, 0], epnts[:, 1])
        ex = ex / 1e3
        ey = ey / 1e3
        #
        # checking edge direction Vs plane perpendicular
        edgv = np.array([np.diff(ex[0:2])[0], np.diff(ey[0:2])[0]])
        chks.append(np.sign(np.cross(ppar[:2], edgv)))
    #
    #
    return np.array(chks)
Example #10
0
def create_faults(mesh, iedge, thickness, rot_angle, sampling):
    """
    Creates a list of profiles at a given angle from a mesh limiting the fault
    at the top. The fault is confined within a seismogenic layer with a
    thickness provided by the user.

    :param numpy.ndarray mesh:
        The mesh defining the top of the slab
    :param int iedge:
        ID of the edge to be used for the construction of the plane
    :param float thickness:
        The thickness [km] of the layer containing the fault
    :param float rot_angle:
        Rotation angle of the new fault (reference is the dip direction of the
        plane interpolation the slab surface)
    :param float sampling:
        The sampling distance used to create the profiles of the virtual
        faults [km]
    :returns:
        A list of :class:`openquake.hazardlib.geo.line.Line` instances
    """
    #
    # save mesh original shape
    shape = mesh[:, :, 0].shape
    #
    # get indexes of the edge
    idxs = np.nonzero(np.isfinite(mesh[iedge, :, 2]))
    #
    # create a 3xn array with the points composing the mesh
    lld = np.array([
        mesh[:, :, 0].flatten('C'), mesh[:, :, 1].flatten('C'),
        mesh[:, :, 2].flatten('C')
    ]).T
    idx = np.isnan(lld[:, 0])

    assert np.nanmax(mesh[:, :, 2]) < 750.

    #
    # project the points using Lambert Conic Conformal - for the reference
    # meridian 'lon_0' we use the mean longitude of the mesh
    melo = _get_mean_longitude(lld[:, 0])
    p = Proj(proj='lcc', lon_0=melo, lat_1=0., lat_2=60.)
    x, y = p(lld[:, 0], lld[:, 1])
    x = x / 1e3  # m -> km
    y = y / 1e3  # m -> km
    x[idx] = np.nan
    y[idx] = np.nan
    #
    # create a np.array with the same shape of the input 'mesh' but with
    # projected coordinates
    tmpx = np.reshape(x, shape, order='C')
    tmpy = np.reshape(y, shape, order='C')
    meshp = np.stack((tmpx, tmpy, mesh[:, :, 2]), axis=2)
    assert np.nanmax(meshp[:, :, 2]) < 750.
    #
    # check if the selected edge is continuous, otherwise split
    if np.all(np.diff(idxs) == 1):
        ilists = [list(idxs[0])]
    else:
        ilists = []
        cnt = 0
        tmp = []
        for i, idx in enumerate(list(idxs[0])):
            if i == 0:
                tmp.append(idx)
            else:
                if idx - tmp[-1] == 1:
                    tmp.append(idx)
                else:
                    ilists.append(tmp)
                    tmp = [idx]
                    cnt += 1
        if len(tmp) > 1:
            ilists.append(tmp)
    #
    # Remove single element lists
    for i, t in enumerate(ilists):
        if len(t) < 2:
            del ilists[i]
    #
    # plane fitting
    tmp = np.vstack(
        (meshp[:, :, 0].flatten(), meshp[:, :,
                                         1].flatten(), meshp[:, :,
                                                             2].flatten())).T
    idx = np.isfinite(tmp[:, 2])
    _, pppar = plane_fit(tmp[idx, :])
    #
    # process the edge
    dlt = 1
    rlow = iedge - dlt
    rupp = iedge + dlt + 1
    plist = []
    clist = []
    #
    # loop over 'segments' composing the edge
    for ilist in ilists:
        temp_plist = []
        check = False
        #
        # loop over the indexes of the nodes composing the edge 'segment' and
        # for each point we create a new profile using the dip angle
        for i, ic in enumerate(ilist):
            loccheck = False
            #
            # initialise the indexes
            clow = ic - dlt
            cupp = ic + dlt + 1
            #
            # fixing indexes at the borders of the mesh
            if rlow < 0:
                rlow = 0
                rupp = rlow + dlt * 2 + 1
            if clow < 0:
                clow = 0
                cupp = clow + dlt * 2 + 1
            if rupp >= meshp.shape[0]:
                rupp = meshp.shape[0] - 1
                rlow = max(0, rupp - (dlt * 2 + 1))
            if cupp >= meshp.shape[1]:
                cupp = meshp.shape[1] - 1
                clow = cupp - (dlt * 2 + 1)
            #
            # coordinates subset
            tmp = np.vstack((meshp[rlow:rupp, clow:cupp, 0].flatten(),
                             meshp[rlow:rupp, clow:cupp,
                                   1].flatten(), meshp[rlow:rupp, clow:cupp,
                                                       2].flatten())).T
            #
            # interpolate the plane
            ii = np.isfinite(tmp[:, 2])
            if np.sum(ii) > 4:
                try:
                    _, ppar = plane_fit(tmp[ii, :])
                except ValueError:
                    print('Plane interpolation failed')
            else:
                ppar = pppar
            #
            # vertical plane with the same strike
            vertical_plane = np.array([ppar[0], ppar[1], 0])
            vertical_plane = vertical_plane / (sum(vertical_plane**2))**.5
            #
            # strike direction
            stk = np.cross(ppar, vertical_plane)
            stk = stk / (sum(stk**2.))**0.5
            #
            # compute the vector on the plane defining the steepest direction
            # https://goo.gl/UtKJxe
            dip = np.cross(ppar, np.cross([0, 0, -1], ppar))
            dip = dip / (sum(dip**2.))**0.5
            #
            # rotate the dip of the angle provided by the user. Note that the
            # rotation follows the right hand rule. The rotation axis is the
            # strike
            dirc = _rotate_vector(dip, stk, rot_angle)
            #
            # compute the points composing the new surface. The new surface
            # is at a distance 'slab_tickness' below the original surface in a
            # direction perpendicular to the fitted planes
            corr = -1
            dstances = np.arange(0, thickness - 0.05 * sampling, sampling)
            xls = meshp[iedge, ic, 0] + corr * dstances * dirc[0]
            yls = meshp[iedge, ic, 1] + corr * dstances * dirc[1]
            zls = meshp[iedge, ic, 2] + corr * dstances * dirc[2]

            # CHECK THIS - This extends the last point of an addictional
            # fraction of distance
            xls[-1] += corr * sampling * 0.1 * dirc[0]
            yls[-1] += corr * sampling * 0.1 * dirc[1]
            zls[-1] += corr * sampling * 0.1 * dirc[2]

            #
            # back-conversion to geographic coordinates
            llo, lla = p(xls * 1e3, yls * 1e3, inverse=True)
            #
            # Check if this profile intersects with the previous one and fix
            # the problem
            if i > 0:
                # last profile stored
                pa = temp_plist[-1][0]
                pb = temp_plist[-1][-1]
                # new profile
                pc = Point(llo[0], lla[0], zls[0])
                pd = Point(llo[-1], lla[-1], zls[-1])
                out = intersect(pa, pb, pc, pd)
                # passes if profiles intersect
                if out:
                    check = True
                    loccheck = True
                    """
                    TODO
                    2018.07.05 - This is experimental code trying to fix
                    intersection of profiles. The current solution simply
                    removes the profiles causing problems.

                    #
                    # compute the direction cosines of the segment connecting
                    # the top vertexes of two consecutive profiles
                    x1, y1 = p(pa.longitude, pa.latitude)
                    x1 /= 1e3
                    y1 /= 1e3
                    z1 = pa.depth
                    x2, y2 = p(mesh[iedge, ic+1, 0], mesh[iedge, ic+1, 1])
                    z2 = mesh[iedge, ic+1, 2]
                    # x2, y2 = p(pc.longitude, pc.latitude)
                    # z2 = pc.depth
                    x2 /= 1e3
                    y2 /= 1e3
                    topdirc, _ = get_dcos(np.array([x1, y1, z1]),
                                          np.array([x2, y2, z2]))
                    # get the direction cosines between the first point of the
                    # new segment and the last point of the previous segment
                    # slightly shifted
                    x1, y1 = p(pc.longitude, pc.latitude)
                    x1 /= 1e3
                    y1 /= 1e3
                    z1 = pc.depth
                    #
                    x2, y2 = p(pb.longitude, pb.latitude)
                    x2 /= 1e3
                    y2 /= 1e3
                    fctor = 5.5
                    x2 += topdirc[0] * fctor * sampling
                    y2 += topdirc[1] * fctor * sampling
                    z2 = pb.depth + topdirc[2] * fctor * sampling
                    tdirc, dst = get_dcos(np.array([x1, y1, z1]),
                                          np.array([x2, y2, z2]))
                    #
                    # new sampling distance
                    # news = dst/len(dstances)
                    # dstances = np.arange(0, dst+0.05*dst, news)
                    dstances = np.arange(0, thickness+0.05*sampling, sampling)
                    xls = meshp[iedge, ic, 0] + dstances * tdirc[0]
                    yls = meshp[iedge, ic, 1] + dstances * tdirc[1]
                    zls = meshp[iedge, ic, 2] + dstances * tdirc[2]
                    #
                    # back-conversion to geographic coordinates
                    llo, lla = p(xls*1e3, yls*1e3, inverse=True)
                    # raise ValueError('Intersecting profiles')
                    """
            #
            # Update the profile list
            assert not np.any(np.isnan(llo))
            assert not np.any(np.isnan(lla))
            assert not np.any(np.isnan(zls))
            line = Line([Point(x, y, z) for x, y, z in zip(llo, lla, zls)])
            if not loccheck:
                temp_plist.append(line)
        #
        # Check if some of the new profiles intersect
        # if check:
        # plot_profiles([temp_plist], mesh)
        check_intersection(temp_plist)
        #
        # updating the list of profiles
        if len(temp_plist) > 1:
            plist.append(temp_plist)
            if check:
                clist.append(temp_plist)

    # if len(clist):
    #     plot_profiles(clist, mesh)

    #
    # Return the list of profiles groups. Each group is a set of lines
    return plist
Example #11
0
def create_lower_surface_mesh(mesh, slab_thickness):
    """
    This method used to build the bottom surface of the slab computes at each
    point the plane fitting a local portion of the top-surface and uses the
    perpendicular to find the corresponding node for the bottom surface.

    :parameter mesh:
        An instance of the :class:`openquake.hazardlib.geo.mesh.Mesh` that
        describes the top of the slab within which we place inslab seismicity
    :parameter slab_thickness:
        Thickness of the slab [km]
    :returns:
        An instance of :class:`openquake.hazardlib.geo.mesh.Mesh`
    """
    #
    # save original shape of the 2.5D mesh
    oshape = mesh.lons.shape
    #
    # project the points using Lambert Conic Conformal - for the reference
    # meridian 'lon_0' we use the mean longitude of the grid
    p = Proj(proj='lcc', lon_0=np.mean(mesh.lons.flatten('F')), lat_2=45)
    x, y = p(mesh.lons.flatten('F'), mesh.lats.flatten('F'))
    x = x / 1e3  # m -> km
    y = y / 1e3  # m -> k
    #
    # reshaping
    x = np.reshape(x, oshape, order='F')
    y = np.reshape(y, oshape, order='F')
    #
    # initialize the lower mesh
    lowm = deepcopy(mesh)
    #
    #
    dlt = 1
    for ir in range(0, x.shape[0]):
        for ic in range(0, x.shape[1]):
            #
            # initialise the indexes
            rlow = ir - dlt
            rupp = ir + dlt + 1
            clow = ic - dlt
            cupp = ic + dlt + 1
            #
            # fixing indexes at the borders of the mesh
            if rlow < 0:
                rlow = 0
                rupp = rlow + dlt * 2 + 1
            if clow < 0:
                clow = 0
                cupp = clow + dlt * 2 + 1
            if rupp >= x.shape[0]:
                rupp = x.shape[0] - 1
                rlow = rupp - (dlt * 2 + 1)
            if cupp >= x.shape[1]:
                cupp = x.shape[1] - 1
                clow = cupp - (dlt * 2 + 1)
            #
            # get the subset of nodes and compute equation of the interpolating
            # plane
            xx = np.vstack((x[rlow:rupp,
                              clow:cupp].flatten(), y[rlow:rupp,
                                                      clow:cupp].flatten(),
                            mesh.depths[rlow:rupp, clow:cupp].flatten())).T

            ii = np.isfinite(xx[:, 2])
            if np.sum(ii) > 4:
                try:
                    pnt, ppar = plane_fit(xx[ii, :])
                except:
                    raise ValueError('Plane interpolation failed')
            #
            # compute the points composing the new surface. The new surface
            # is at a distance 'slab_tickness' below the original surface in a
            # direction perpendicular to the fitted planes
            corr = 1
            if np.sign(ppar[2]) == -1:
                corr = -1
            xls = x[ir, ic] + corr * slab_thickness * ppar[0]
            yls = y[ir, ic] + corr * slab_thickness * ppar[1]
            zls = mesh.depths[ir, ic] + corr * slab_thickness * ppar[2]
            #
            # back-conversion to geographic coordinates
            llo, lla = p(xls * 1e3, yls * 1e3, inverse=True)
            #
            # updating the mesh
            lowm.lons[ir, ic] = llo
            lowm.lats[ir, ic] = lla
            lowm.depths[ir, ic] = zls
    #
    #
    return lowm
Example #12
0
def regularize(mesh, spacing):
    """
    TODO
    2018.05.18 - This is currenly not used. Consider removing.

    Fitting https://gist.github.com/amroamroamro/1db8d69b4b65e8bc66a6
    """
    #
    #
    dlt_x = 10
    dlt_z = 10
    #
    # create a 3xn array with the points composing the mesh
    lld = np.array([
        mesh.lons.flatten('F'),
        mesh.lats.flatten('F'),
        mesh.depths.flatten('F')
    ]).T
    #
    # project the points using Lambert Conic Conformal - for the reference
    # meridian 'lon_0' we use the mean longitude of the mesh
    p = Proj(proj='lcc', lon_0=np.mean(lld[:, 0]), lat_2=45)
    x, y = p(lld[:, 0], lld[:, 1])
    x = x / 1e3  # m -> km
    y = y / 1e3  # m -> km
    #
    # compute the distances between all the points composing the mesh and
    # a reference point indicated with the index 'idx'
    idx = 0
    dx = np.sign(lld[idx, 0] - lld[:, 0]) * ((lld[idx, 0] - lld[:, 0])**2 +
                                             (lld[idx, 1] - lld[:, 1])**2)**.5
    dz = (lld[idx, 2] - lld[:, 2])
    #
    # find nodes within the delta X and delta Z distances
    idx = np.nonzero((dx <= dlt_x) & (dz <= dlt_z))
    #
    # compute the equation of the plane fitting the portion of the slab surface
    xx = np.vstack((x[idx].T, y[idx].T, lld[idx, 2])).T
    pnt, ppar = plane_fit(xx)
    #
    # vertical plane
    vertical_plane = [ppar[0], ppar[1], 0]
    vertical_plane = vertical_plane / (sum(vertical_plane)**2)**.5
    #
    # strike direction
    stk = np.cross(ppar, vertical_plane)
    stk = stk / sum(stk**2.)**0.5
    #
    # project the top left point on the plane surface. First we compute
    # the distance from the point to the plane then we find the coordinates
    # of the point. TODO
    t = -np.sum(ppar * lld[0, :]) / np.sum(ppar**2)
    orig = np.array(
        [ppar[0] * t + x[0], ppar[1] * t + y[0], ppar[2] * t + lld[0, 2]])
    #
    # compute the vector on the plane defining the steepest direction
    # https://www.physicsforums.com/threads/projecting-a-vector-onto-a-plane.496184/
    dip = np.cross(ppar, np.cross([0, 0, -1], ppar))
    dip = dip / sum(dip**2.)**0.5
    #
    # create the rectangle in 3D
    rects = []
    pnt0 = [x[0], y[0], lld[0, 2]]
    pnt1 = [
        x[0] + stk[0] * dlt_x, y[0] + stk[1] * dlt_x,
        lld[0, 2] + stk[2] * dlt_x
    ]
    pnt2 = [
        pnt1[0] + dip[0] * dlt_z, pnt1[1] + dip[1] * dlt_z,
        pnt1[2] + dip[2] * dlt_z
    ]
    pnt3 = [
        x[0] + dip[0] * dlt_z, y[0] + dip[1] * dlt_z,
        lld[0, 2] + dip[2] * dlt_z
    ]

    rects.append([pnt0, pnt1, pnt2, pnt3])
    rects = np.array(rects)

    # lo, la = p(, lld[:,1])

    return rects