easyidp.pix4d.Pix4D#

class easyidp.pix4d.Pix4D(project_path=None, raw_img_folder=None, param_folder=None)#

A Pix4D class, contains information of 3D reconstruction.

__init__(project_path=None, raw_img_folder=None, param_folder=None)#

The method to initialize the Pix4D class

Parameters:
  • project_path (str, optional) – The pix4d project file to open, like “xxxx.p4d”, by default None, means create an empty class

  • raw_img_folder (str, optional) – the original UAV image folder, by default None

  • param_folder (str, optional) – the folder of pix4d project parameters, just in case user changed the default folder structure (...\project_name\1_initial\params\), by default None

Example

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

Then open the demo pix4d project:

>>> p4d = idp.Pix4D(test_data.pix4d.maize_folder)

Or manual specify parameters if the project folder structure has been changed.

>>> p4d = idp.Pix4D(
...     project_path   = test_data.pix4d.lotus_folder,
...     raw_img_folder = test_data.pix4d.lotus_photos,
...     param_folder   = test_data.pix4d.lotus_param
... )

Or you can create an empty project, and the open a given path:

>>> p4d = idp.Pix4D()
>>> p4d.open_project(
...     project_path   = test_data.pix4d.lotus_folder,
...     raw_img_folder = test_data.pix4d.lotus_photos,
...     param_folder   = test_data.pix4d.lotus_param
... )

Caution

In previous case, the manager reorganized the project structure and outputs of test_data.pix4d.lotus_folder

(e.g., moved the \project_name\1_initial\params\ to \project_name\params\, as well as other outputs, the following folder is no more a standard pix4d project)

$ls '/Users/<user>/Library/Application Support/easyidp.data/data_for_tests/pix4d/lotus_tanashi_full'

params/
photos/
hasu_tanashi_20170525_Ins1RGB_30m_dsm.tif
hasu_tanashi_20170525_Ins1RGB_30m_group1_densified_point_cloud.ply
hasu_tanashi_20170525_Ins1RGB_30m_transparent_mosaic_group1.tif
plot_dom.tif
plot_dsm.tif
plot_pcd.ply

The default loading doesn’t work, because it is not a standard pix4d project:

>>> p4d = idp.Pix4D(test_data.pix4d.lotus_folder)

Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/Users/hwang/OneDrive/Program/GitHub/EasyIDP/easyidp/pix4d.py", line 48, in __init__
    File "/Users/hwang/OneDrive/Program/GitHub/EasyIDP/easyidp/pix4d.py", line 56, in open_project
        hasu_tanashi_20170525_Ins1RGB_30m_group1_densified_point_cloud.ply
    File "/Users/hwang/OneDrive/Program/GitHub/EasyIDP/easyidp/pix4d.py", line 829, in parse_p4d_project
        sub_folder = os.listdir(project_path)
FileNotFoundError: Can not find pix4d parameter in given project folder

In this case, must manual specfiy the param_folder

Methods

__init__([project_path, raw_img_folder, ...])

The method to initialize the Pix4D class

back2raw(roi[, save_folder])

Projects several GIS coordintates ROIs (polygons) to all images

back2raw_crs(points_xyz[, distort_correct, ...])

Projects one GIS coordintates ROI (polygon) to all images

get_photo_position([to_crs, refresh])

Get all photos' center geo position (on given CRS)

load_dom(geotiff_path)

Manual load the DOM file generated by this Pix4D project

load_dsm(geotiff_path)

Manual load the DSM file generated by this Pix4D project

load_pcd(pcd_path)

Manual load the point cloud file generated by this Pix4D project

open_project(project_path[, raw_img_folder, ...])

Open a new 3D reconstructin project to overwritting current project.

show_roi_on_img(img_dict, roi_name[, img_name])

Visualize the specific backward projection results for given roi on the given image.

sort_img_by_distance(img_dict_all, roi[, ...])

Advanced wrapper of sorting back2raw img_dict results by distance from photo to roi

Attributes

crs

the geographic coordinates (often the same as the export DOM and DSM), <class 'pyproj.crs.crs.CRS'>

dom

The output digitial orthomosaic map (DOM), easyidp.GeoTiff

dsm

The output digitial surface map (DSM), easyidp.GeoTiff

pcd

The output point cloud, easyidp.PointCloud

software

the 3D reconstruction project software, in ['pix4d', 'metashape'], <class 'str'>

offset_np

pix4d point cloud offset

label

project / chunk name

meta

project meta information

enabled

whether this project is activated, (often for Metashape), <class 'bool'>

sensors

the container for all sensors in this project (camera model), <class 'easyidp.Container'>

photos

the container for all photos used in this project (images), <class 'easyidp.Container'>

back2raw(roi, save_folder=None, **kwargs)#

Projects several GIS coordintates ROIs (polygons) to all images

Parameters:
  • roi (easyidp.ROI | dict) – the <ROI> object created by easyidp.ROI() or dictionary

  • save_folder (str, optional) – the folder to save projected preview images and json files, by default “”

  • distortion_correct (bool, optional) – Whether do distortion correction, by default True (back to raw image); If back to software corrected images without len distortion, set it to True. Pix4D support do this operation, seems metashape not supported yet.

  • ignore (str | None, optional) –

    Whether tolerate small parts outside image, check easyidp.reconstruct.Sensor.in_img_boundary() for more details.

    • None: strickly in image area;

    • x: only y (vertical) in image area, x can outside image;

    • y: only x (horizontal) in image area, y can outside image.

  • log (bool, optional) – whether print log for debugging, by default False

Example

Data prepare

>>> import easyidp as idp

>>> lotus = idp.data.Lotus()

>>> p4d = idp.Pix4D(project_path=lotus.pix4d.project,
                    raw_img_folder=lotus.photo,
                    param_folder=lotus.pix4d.param)

>>> roi = idp.ROI(lotus.shp, name_field=0)
[shp][proj] Use projection [WGS 84] for loaded shapefile [plots.shp]
Read shapefile [plots.shp]: 100%|███████████████| 112/112 [00:00<00:00, 2481.77it/s]
>>> roi = roi[0:2]

>>> roi.get_z_from_dsm(lotus.pix4d.dsm)

Then using this function to do backward projection:

>>> out_all = p4d.back2raw(roi)
{
    'N1W1': {
        'DJI_0479':
            array([[  52.9824393 , 1253.05643133],
                   [  79.20465849,  979.90831093],
                   [ 363.27656888, 1000.07501881],
                   [ 337.25115499, 1273.15285336],
                   [  52.9824393 , 1253.05643133]]),
        'DJI_0480':
            array([...])
        ...
    }
    'N1W2': {...}   # output of `back2raw_crs()`
}
back2raw_crs(points_xyz, distort_correct=True, ignore=None, log=False)#

Projects one GIS coordintates ROI (polygon) to all images

Parameters:
  • points_hv (ndarray (nx3)) – The 3D coordinates of polygon vertexm, in CRS coordinates

  • distortion_correct (bool, optional) – Whether do distortion correction, by default True (back to raw image); If back to software corrected images without len distortion, set it to True. Pix4D support do this operation, seems metashape not supported yet.

  • ignore (str | None, optional) –

    Whether tolerate small parts outside image, check easyidp.reconstruct.Sensor.in_img_boundary() for more details.

    • None: strickly in image area;

    • x: only y (vertical) in image area, x can outside image;

    • y: only x (horizontal) in image area, y can outside image.

  • log (bool, optional) – whether print log for debugging, by default False

Returns:

a dictionary like {img_name: pixel coordinate, ... }

Return type:

dict,

Example

Data preparation

>>> import easyidp as idp

>>> p4d = idp.Pix4D(
...     test_data.pix4d.lotus_folder,
...     raw_img_folder=test_data.pix4d.lotus_photos,
...     param_folder=test_data.pix4d.lotus_param
... )

>>> plot =  np.array([   # N1E1 plot geo coordinate
...     [ 368020.2974959 , 3955511.61264302,      97.56272272],
...     [ 368022.24288365, 3955512.02973983,      97.56272272],
...     [ 368022.65361232, 3955510.07798313,      97.56272272],
...     [ 368020.69867274, 3955509.66725421,      97.56272272],
...     [ 368020.2974959 , 3955511.61264302,      97.56272272]
... ])

Then use this function to find the previous ROI positions on the raw images:

>>> out_dict = p4d.back2raw_crs(plot, distort_correct=True)

>>> out_dict["DJI_0177"]
array([[ 137.10982937, 2359.55887614],
       [ 133.56116243, 2107.13954299],
       [ 384.767746  , 2097.05639105],
       [ 388.10993307, 2350.41225998],
       [ 137.10982937, 2359.55887614]])
enabled#

whether this project is activated, (often for Metashape), <class 'bool'>

get_photo_position(to_crs=None, refresh=False)#

Get all photos’ center geo position (on given CRS)

Parameters:
  • to_crs (pyproj.CRS, optional) – Transformed to another geo coordinate, by default None, the project.crs

  • refresh (bool, optional) –

    • False : Use cached results (if have), by default

    • True : recalculate the photo position

Returns:

The dictionary contains “photo.label”: [x, y, z] coordinates

Return type:

dict

Example

Data prepare

>>> import easyidp as idp

>>> lotus = idp.data.Lotus()
>>> p4d = idp.Pix4D(lotus.pix4d.project,lotus.photo, lotus.pix4d.param)

Then use this function to get the photo position in 3D world:

>>> out = p4d.get_photo_position()
{
    'DJI_0422.JPG': array([ 368016.23334752, 3955491.97729229,     138.25541246]),
    'DJI_0423.JPG': array([ 368016.33261375, 3955492.15845851,     138.05762001]),
    ...
}
label#

project / chunk name

load_dom(geotiff_path)#

Manual load the DOM file generated by this Pix4D project

Parameters:

geotiff_path (str) – The path to DOM file

Example

>>> import easyidp as idp
>>> ... # define your project_path and param_folder path
>>> p4d = idp.Pix4D(project_path, param_folder)
>>> p4d.dom
None
>>> p4d.load_dom(dom_path)
>>> p4d.dom
<easyidp.GeoTiff> object
load_dsm(geotiff_path)#

Manual load the DSM file generated by this Pix4D project

Parameters:

geotiff_path (str) – The path to DSM file

Example

>>> import easyidp as idp
>>> ... # define your project_path and param_folder path
>>> p4d = idp.Pix4D(project_path, param_folder)
>>> p4d.dsm
None
>>> p4d.load_dsm(dsm_path)
>>> p4d.dsm
<easyidp.GeoTiff> object
load_pcd(pcd_path)#

Manual load the point cloud file generated by this Pix4D project

Parameters:

pcd_path (str) – The path to point cloud file

Caution

The pix4d produced point cloud is offsetted (xyz-offset). This function already handle adding offset back to point cloud. If you need manual specify idp.PointCloud() by yourself, please do:

>>> p4d = idp.Pix4D(project_path, param_folder)

# wrong
>>> pcd = idp.PointCloud(lotus_full_pcd)

# correct
>>> pcd = idp.PointCloud(lotus_full_pcd, offset=p4d.meta['p4d_offset'])
# or
>>> pcd = idp.PointCloud(lotus_full_pcd, offset=p4d.offset_np)

Example

>>> import easyidp as idp
>>> ... # define your project_path and param_folder path
>>> p4d = idp.Pix4D(project_path, param_folder)
>>> p4d.pcd
None
>>> p4d.load_pcd(pcd_path)
>>> p4d.pcd
<easyidp.PointCloud> object
meta#

project meta information

offset_np#

pix4d point cloud offset

open_project(project_path, raw_img_folder=None, param_folder=None)#

Open a new 3D reconstructin project to overwritting current project.

Parameters:
  • project_path (str) – The pix4d project file to open, like “xxxx.p4d”, or “xxxx” without suffix

  • raw_img_folder (str, optional) – the original UAV image folder, by default None

  • param_folder (str, optional) – the folder of pix4d project parameters, just in case user changed the default folder structure (...\project_name\1_initial\params\), by default None

Example

>>> import easyidp as idp
>>> test_data = idp.data.TestData()

Then using this function to open a new project:

>>> p4d = idp.Pix4D()
>>> p4d.open_project(
...     project_path   = test_data.pix4d.lotus_folder,
...     raw_img_folder = test_data.pix4d.lotus_photos,
...     param_folder   = test_data.pix4d.lotus_param
... )
photos#

the container for all photos used in this project (images), <class 'easyidp.Container'>

sensors#

the container for all sensors in this project (camera model), <class 'easyidp.Container'>

show_roi_on_img(img_dict, roi_name, img_name=None, **kwargs)#

Visualize the specific backward projection results for given roi on the given image.

Parameters:
  • img_dict (dict) – The backward results from back2raw()

  • roi_name (str) – The roi name to show

  • img_name (str) – the image file name. by default None, plotting all available images

  • corrected_poly_coord (np.ndarray, optional) – the corrected 2D polygon pixel coordiante on the image (if have), by default None

  • title (str, optional) – The image title displayed on the top, by default None -> ROI [roi_name] on [img_name]

  • save_as (str, optional) – file path to save the output figure, by default None

  • show (bool, optional) – whether display (in jupyter notebook) or popup (in command line) the figure, by default False

  • color (str, optional) – the polygon line color, by default ‘red’

  • alpha (float, optional) – the polygon transparency, by default 0.5

  • dpi (int, optional) – the dpi of produced figure, by default 72

Example

>>> img_dict_ms = roi.back2raw(ms)

Check the “N1W1” ROI on image “DJI_0479.JPG”:

>>> ms.show_roi_on_img(img_dict_ms, "N1W1", "DJI_0479")

Check the “N1W1” ROI on all available images:

>>> ms.show_roi_on_img(img_dict_ms, "N1W1")

For more details, please check in this example

software#

the 3D reconstruction project software, in [‘pix4d’, ‘metashape’], <class 'str'>

sort_img_by_distance(img_dict_all, roi, distance_thresh=None, num=None, save_folder=None)#

Advanced wrapper of sorting back2raw img_dict results by distance from photo to roi

Parameters:
  • img_dict_all (dict) – All output dict of roi.back2raw(…) e.g. img_dict = roi.back2raw(…) -> img_dict

  • roi (idp.ROI) – Your roi variable

  • num (None or int) – Keep the closest {x} images

  • distance_thresh (None or float) – Keep the images closer than this distance to ROI.

  • save_folder (str, optional) – the folder to save json files and parts of ROI on raw images, by default None

Returns:

the same structure as output of roi.back2raw(…)

Return type:

dict

Example

In the previous back2raw() results :

>>> out_all = p4d.back2raw(roi)
{
    'N1W1': {
        'DJI_0479':
            array([[  52.9824393 , 1253.05643133],
                   [  79.20465849,  979.90831093],
                   [ 363.27656888, 1000.07501881],
                   [ 337.25115499, 1273.15285336],
                   [  52.9824393 , 1253.05643133]]),
        'DJI_0480':
            array([...])
        ...
    }
    'N1W2': {...}   # output of `back2raw_crs()`
}

The image are in chaos order, in most application cases, probable only 1-3 closest images (to ROI in real world) are required, so this function is provided to sort/filter out.

In the following example, it filtered 3 images whose distance from camera to ROI in real world smaller than 10m:

>>> img_dict_sort = p4d.sort_img_by_distance(
...     out_all, roi,
...     distance_thresh=10,  # distance threshold is 10m
...     num=3   # only keep 3 closest images
... )

>>> img_dict_sort
{
    'N1W1': {
        'DJI_0500': array([[1931.09279469, 2191.59919979],
                           [1939.92139124, 1930.65101348],
                           [2199.9439422 , 1939.32128527],
                           [2191.19230849, 2200.557026  ],
                           [1931.09279469, 2191.59919979]]),
        'DJI_0517': array([[2870.94915401, 2143.3570243 ],
                           [2596.8790503 , 2161.04730612],
                           [2578.87033498, 1886.89058023],
                           [2853.13891851, 1869.99769984],
                           [2870.94915401, 2143.3570243 ]]),
        'DJI_0518': array([[3129.43264924, 1984.91814896],
                           [2856.71879306, 2002.03817639],
                           [2838.71418138, 1730.00287388],
                           [3111.73360179, 1713.76233134],
                           [3129.43264924, 1984.91814896]])
    },
    'N1W2': {
        'DJI_0500': array([[2214.36789052, 2200.35979344],
                           [2221.8996575 , 1940.70687713],
                           [2479.9825464 , 1949.3909589 ],
                           [2472.52171907, 2209.40355333],
                           [2214.36789052, 2200.35979344]]),
        'DJI_0517': array([[2849.82108263, 1845.6733702 ],
                           [2577.37309441, 1863.60741328],
                           [2559.80046778, 1592.07656949],
                           [2832.52942622, 1574.92640413],
                           [2849.82108263, 1845.6733702 ]]),
        'DJI_0516': array([[2891.61686486, 2542.98979632],
                           [2616.06780032, 2559.41601014],
                           [2598.43900454, 2282.36641612],
                           [2874.23023492, 2266.71552931],
                           [2891.61686486, 2542.98979632]])
    }
}

Or pick the closest one image:

>>> img_dict_sort = p4d.sort_img_by_distance(
...     out_all, roi,
...     num=1   # only keep 1 closest images
... )

>>> img_dict_sort
{
    'N1W1': {
        'DJI_0500': array([[1931.09279469, 2191.59919979],
                           [1939.92139124, 1930.65101348],
                           [2199.9439422 , 1939.32128527],
                           [2191.19230849, 2200.557026  ],
                           [1931.09279469, 2191.59919979]])
    },
    'N1W2': {
        'DJI_0500': array([[2214.36789052, 2200.35979344],
                           [2221.8996575 , 1940.70687713],
                           [2479.9825464 , 1949.3909589 ],
                           [2472.52171907, 2209.40355333],
                           [2214.36789052, 2200.35979344]])
    }
}

You can use list(dict.keys())[0] to get the image name automatically to iterate each plot:

for plot_name, plot_value in img_dict_sort.items():
    img_name = list(plot_value.key())[0]

    coord = plot_value[img_name]
    # or
    coord = img_dict_sort[plot_name][img_name]