## Python Open Photogrammetry Format (OPF)
This repository provides a Python package for reading, writing and manipulating projects in the OPF format.
For more information about what OPF is and its full specification, please refer to https://www.github.com/Pix4D/opf-spec
### Installation
The library can be installed using `pip` with the following command:
```shell
pip install pyopf
```
The additional command line tool dependencies are available through a package extra, and can be installed like so:
```shell
pip install pyopf[tools]
```
### Structure of the PyOPF repository
The `pyopf` library can be found under `src/pyopf`. The library implements easy parsing and writing of OPF projects in Python.
Below is a small example, printing the calibrated position and orientation of a camera, knowing its ID.
```python
from pyopf.io import load
from pyopf.resolve import resolve
from pyopf.uid64 import Uid64
# Path to the example project file.
project_path = "spec/examples/project.opf"
# We are going to search for the calibrated position of the camera with this ID
camera_id = Uid64(hex = "0x2D1A1DE")
# Load the json data and resolve the project, i.e. load the project items as named attributes.
project = load(project_path)
project = resolve(project)
# Many objects are optional in OPF. If they are missing, they are set to None.
if project.calibration is None:
print("No calibration data.")
exit(1)
# Filter the list of calibrated cameras to find the one with the ID we are looking for.
calibrated_camera = [camera for camera in project.calibration.calibrated_cameras.cameras if camera.id == camera_id]
# Print the pose of the camera.
if calibrated_camera:
print("The camera {} is calibrated at:".format(camera_id), calibrated_camera[0].position)
print("with orientation", calibrated_camera[0].orientation_deg)
else:
print("There is no camera with id: {} in the project".format(camera_id))
```
The custom attributes are stored per node in the `custom_attributes` dictionary. This dictionary might be `None` if
the `Node` has no associated custom attributes. Below is an example of setting a custom attribute.
```python
import numpy as np
from pathlib import Path
from pyopf.pointcloud import GlTFPointCloud
pcl = GlTFPointCloud.open(Path('dense_pcl/dense_pcl.gltf'))
# Generate a new point attribute as a random vector of 0s and 1s
# The attribute must have one scalar per point
new_attribute = np.random.randint(0, 2, size=len(pcl.nodes[0]))
# The attribute must have the shape (number_of_points, 1)
new_attribute = new_attribute.reshape((-1, 1))
# Supported types for custom attributes are np.float32, np.uint32, np.uint16, np.uint8
new_attribute = new_attribute.astype(np.uint32)
# Set the new attribute as a custom attribute for the node
# By default, nodes might be missing custom attributes, so the dictionary might have to be created
if pcl.nodes[0].custom_attributes is not None:
pcl.nodes[0].custom_attributes['point_class'] = new_attribute
else:
pcl.nodes[0].custom_attributes = {'point_class': new_attribute}
pcl.write(Path('out/out.gltf'))
```
### OPF Tools
We provide a few tools as command line scripts to help manipulate OPF projects in different ways.
#### Undistorting
A tool to undistort images is provided. The undistorted images will be stored in their original location, but in an `undistort` directory. Only images taken with a perspective camera, for which the sensor has been calibrated will be undistorted.
This tool can be used as
`opf_undistort project.opf`
#### Cropping
We call "cropping" the operation of preserving only the region of interest of the project (as defined by the Region of
Interest OPF extension).
The project to be cropped *MUST* contain an item of type `ext_pix4d_region_of_interest`.
During the cropping process, only the control points and the part of the point clouds which are contained in the ROI are kept.
Cameras which do not see any remaining points from the point clouds are discarded.
Also, cropping uncalibrated projects is not supported.
The following project items are updated during cropping:
* Point Clouds (including tracks)
* Cameras (input, projected, calibrated, camera list)
* GCPs
The rest of the project items are simply copied.
The cropping tool can be called using
`opf_crop project_to_crop.opf output_directory`
#### Convert to COLMAP model
A tool to convert an OPF project to a COLMAP sparse model. COLMAP sparse models consist of three files `cameras.txt`, `images.txt`, and `points3D.txt`:
* `cameras.txt` contains information about the sensors, such as intrinsic parameters and distortion.
* `images.txt` contains information about the cameras, such as extrinsic parameters and the corresponding image filename.
* `points3D.txt` contains information about the tracks, such as their position and color.
The tool can also be used to copy the images to a new directory, by specifying the `--out-img-dir` parameter. If specified, the tree structure of where input images are stored will be copied to the output image directory. In other words, if all images are stored in the same directory, the folder specified by `--out-img-dir` will only contain the images. If images are stored in different folders/subfolders, the `--out-img-dir` folder will contain the same folders/subfolders starting from the first common folder.
Only calibrated projects with only perspective cameras are supported. Remote files are not supported.
The conversion can be done by calling
`opf2colmap project.opf`
#### Convert to NeRF
This tool converts OPF projects to NeRF. NeRF consists of transforms file(s), which contain information about distortion, intrinsic and extrinsic parameters of cameras. Usually it is split in `transforms_train.json` and `transforms_test.json` files, but can sometimes also have only the train one. The split can be controlled with the parameter `--train-frac`, for example `--train-frac 0.7` will randomly assign 70% of images for training, and the remaining 30% for testing. If this parameter is unspecified or set to 1.0, only the `transforms_train.json` will be generated. Sometimes an additional `transforms_val.json` is required. It is to evaluate from new points of view, but the generation of new point of views is not managed by this tool, so it can just be a copy of `transforms_test.json` renamed.
The tool can also convert input images to other image formats using `--out-img-format`. An optional output directory can be given with `--out-img-dir`, otherwise the images are written to the same directory as the input ones. If `--out-img-dir` is used without `--out-img-format`, images will be copied. When copying or converting an image, the input directory layout is preserved.
When `--out-img-dir` is used, the tree structure of where input images are stored will be copied to the output image directory. In other words, if all images are stored in the same directory, the folder specified by `--out-img-dir` will only contain the images. If images are stored in different folders/subfolders, the `--out-img-dir` folder will contain the same folders/subfolders starting from the first common folder.
Only calibrated projects with perspective cameras are supported.
##### Examples
Different NeRFs require different parameter settings, here are some popular examples:
- **Instant-NeRF**:
By default all values are set to work with Instant-NeRF, so it can be used as:
`opf2nerf project.opf --output-extension`
- **Nerfstudio**:
Nerfstudio is another popular tool. The converter has a parameter to use the proper options:
`opf2nerf project.opf --out-dir out_dir/ --nerfstudio`
- **DirectVoxGo**:
DirectVoxGo only works with PNG image files, and contrary to Instant-NeRF it doesn't flip cameras orientation with respect to OPF. Thus it can be used as:
`opf2nerf project.opf --out-img-format png --out-img-dir ./images --no-camera-flip`
#### Convert to LAS
A tool converting an OPF project's point clouds to LAS. One output for each dense and sparse point cloud will be produced.
It can be used as follows:
`opf2las path_to/project.opf --out-dir your_output_dir`
#### Convert to PLY
A tool converting an OPF project's point clouds to PLY. One output for each dense and sparse point cloud will be produced.
It can be used as follows:
`opf2ply path_to/project.opf --out-dir your_output_dir`
### Examples
We provide also a few examples of command line scripts to illustrate and educate about various photogrammetry knowledge using the OPF projects.
#### Compute reprojection error
This script computes the reprojection error of input GCPs in calibrated cameras using the OPF project as an input.
`python examples/compute_reprojection_error.py --opf_path path_to/project.opf`
## License and citation
If you use this work in your research or projects, we kindly request that you cite it as follows:
The Open Photogrammetry Format Specification, Grégoire Krähenbühl, Klaus Schneider-Zapp, Bastien Dalla Piazza, Juan Hernando, Juan Palacios, Massimiliano Bellomo, Mohamed-Ghaïth Kaabi, Christoph Strecha, Pix4D, 2023, retrieved from https://pix4d.github.io/opf-spec/
Copyright (c) 2023 Pix4D SA
All scripts and/or code contained in this repository are licensed under Apache License 2.0.
Third party documents or tools that are used or referred to in this specification are licensed under their own terms by their respective copyright owners.
Raw data
{
"_id": null,
"home_page": "https://pix4d.github.io/opf-spec/specification/project.html",
"name": "pyopf",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "photogrammetry, OPF",
"author": "Pix4D",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/b8/60/66dd38d2bf89fd102d2e366a317c16e754ab0bd3c1a869c81aee44d79751/pyopf-1.4.0.tar.gz",
"platform": null,
"description": "## Python Open Photogrammetry Format (OPF)\n\nThis repository provides a Python package for reading, writing and manipulating projects in the OPF format.\nFor more information about what OPF is and its full specification, please refer to https://www.github.com/Pix4D/opf-spec\n\n### Installation\n\nThe library can be installed using `pip` with the following command:\n\n```shell\npip install pyopf\n```\n\nThe additional command line tool dependencies are available through a package extra, and can be installed like so:\n\n```shell\npip install pyopf[tools]\n```\n\n### Structure of the PyOPF repository\n\nThe `pyopf` library can be found under `src/pyopf`. The library implements easy parsing and writing of OPF projects in Python.\n\nBelow is a small example, printing the calibrated position and orientation of a camera, knowing its ID.\n\n```python\nfrom pyopf.io import load\n\nfrom pyopf.resolve import resolve\nfrom pyopf.uid64 import Uid64\n\n# Path to the example project file.\nproject_path = \"spec/examples/project.opf\"\n\n# We are going to search for the calibrated position of the camera with this ID\ncamera_id = Uid64(hex = \"0x2D1A1DE\")\n\n# Load the json data and resolve the project, i.e. load the project items as named attributes.\nproject = load(project_path)\nproject = resolve(project)\n\n# Many objects are optional in OPF. If they are missing, they are set to None.\nif project.calibration is None:\n print(\"No calibration data.\")\n exit(1)\n\n# Filter the list of calibrated cameras to find the one with the ID we are looking for.\ncalibrated_camera = [camera for camera in project.calibration.calibrated_cameras.cameras if camera.id == camera_id]\n\n# Print the pose of the camera.\nif calibrated_camera:\n print(\"The camera {} is calibrated at:\".format(camera_id), calibrated_camera[0].position)\n print(\"with orientation\", calibrated_camera[0].orientation_deg)\nelse:\n print(\"There is no camera with id: {} in the project\".format(camera_id))\n```\n\nThe custom attributes are stored per node in the `custom_attributes` dictionary. This dictionary might be `None` if\nthe `Node` has no associated custom attributes. Below is an example of setting a custom attribute.\n\n```python\nimport numpy as np\nfrom pathlib import Path\nfrom pyopf.pointcloud import GlTFPointCloud\n\npcl = GlTFPointCloud.open(Path('dense_pcl/dense_pcl.gltf'))\n\n# Generate a new point attribute as a random vector of 0s and 1s\n# The attribute must have one scalar per point\nnew_attribute = np.random.randint(0, 2, size=len(pcl.nodes[0]))\n\n# The attribute must have the shape (number_of_points, 1)\nnew_attribute = new_attribute.reshape((-1, 1))\n# Supported types for custom attributes are np.float32, np.uint32, np.uint16, np.uint8\nnew_attribute = new_attribute.astype(np.uint32)\n\n# Set the new attribute as a custom attribute for the node\n# By default, nodes might be missing custom attributes, so the dictionary might have to be created\nif pcl.nodes[0].custom_attributes is not None:\n pcl.nodes[0].custom_attributes['point_class'] = new_attribute\nelse:\n pcl.nodes[0].custom_attributes = {'point_class': new_attribute}\n\npcl.write(Path('out/out.gltf'))\n```\n\n### OPF Tools\n\nWe provide a few tools as command line scripts to help manipulate OPF projects in different ways.\n\n#### Undistorting\n\nA tool to undistort images is provided. The undistorted images will be stored in their original location, but in an `undistort` directory. Only images taken with a perspective camera, for which the sensor has been calibrated will be undistorted.\n\nThis tool can be used as\n\n`opf_undistort project.opf`\n\n#### Cropping\n\nWe call \"cropping\" the operation of preserving only the region of interest of the project (as defined by the Region of\nInterest OPF extension).\nThe project to be cropped *MUST* contain an item of type `ext_pix4d_region_of_interest`.\n\nDuring the cropping process, only the control points and the part of the point clouds which are contained in the ROI are kept.\nCameras which do not see any remaining points from the point clouds are discarded.\nAlso, cropping uncalibrated projects is not supported.\n\nThe following project items are updated during cropping:\n* Point Clouds (including tracks)\n* Cameras (input, projected, calibrated, camera list)\n* GCPs\n\nThe rest of the project items are simply copied.\n\nThe cropping tool can be called using\n\n`opf_crop project_to_crop.opf output_directory`\n\n#### Convert to COLMAP model\n\nA tool to convert an OPF project to a COLMAP sparse model. COLMAP sparse models consist of three files `cameras.txt`, `images.txt`, and `points3D.txt`:\n* `cameras.txt` contains information about the sensors, such as intrinsic parameters and distortion.\n* `images.txt` contains information about the cameras, such as extrinsic parameters and the corresponding image filename.\n* `points3D.txt` contains information about the tracks, such as their position and color.\n\nThe tool can also be used to copy the images to a new directory, by specifying the `--out-img-dir` parameter. If specified, the tree structure of where input images are stored will be copied to the output image directory. In other words, if all images are stored in the same directory, the folder specified by `--out-img-dir` will only contain the images. If images are stored in different folders/subfolders, the `--out-img-dir` folder will contain the same folders/subfolders starting from the first common folder.\n\nOnly calibrated projects with only perspective cameras are supported. Remote files are not supported.\n\nThe conversion can be done by calling\n\n`opf2colmap project.opf`\n\n#### Convert to NeRF\n\nThis tool converts OPF projects to NeRF. NeRF consists of transforms file(s), which contain information about distortion, intrinsic and extrinsic parameters of cameras. Usually it is split in `transforms_train.json` and `transforms_test.json` files, but can sometimes also have only the train one. The split can be controlled with the parameter `--train-frac`, for example `--train-frac 0.7` will randomly assign 70% of images for training, and the remaining 30% for testing. If this parameter is unspecified or set to 1.0, only the `transforms_train.json` will be generated. Sometimes an additional `transforms_val.json` is required. It is to evaluate from new points of view, but the generation of new point of views is not managed by this tool, so it can just be a copy of `transforms_test.json` renamed.\n\nThe tool can also convert input images to other image formats using `--out-img-format`. An optional output directory can be given with `--out-img-dir`, otherwise the images are written to the same directory as the input ones. If `--out-img-dir` is used without `--out-img-format`, images will be copied. When copying or converting an image, the input directory layout is preserved.\n\nWhen `--out-img-dir` is used, the tree structure of where input images are stored will be copied to the output image directory. In other words, if all images are stored in the same directory, the folder specified by `--out-img-dir` will only contain the images. If images are stored in different folders/subfolders, the `--out-img-dir` folder will contain the same folders/subfolders starting from the first common folder.\n\nOnly calibrated projects with perspective cameras are supported.\n\n##### Examples\n\nDifferent NeRFs require different parameter settings, here are some popular examples:\n\n- **Instant-NeRF**:\n By default all values are set to work with Instant-NeRF, so it can be used as:\n\n `opf2nerf project.opf --output-extension`\n\n- **Nerfstudio**:\n Nerfstudio is another popular tool. The converter has a parameter to use the proper options:\n\n `opf2nerf project.opf --out-dir out_dir/ --nerfstudio`\n\n- **DirectVoxGo**:\n DirectVoxGo only works with PNG image files, and contrary to Instant-NeRF it doesn't flip cameras orientation with respect to OPF. Thus it can be used as:\n\n `opf2nerf project.opf --out-img-format png --out-img-dir ./images --no-camera-flip`\n\n#### Convert to LAS\n\nA tool converting an OPF project's point clouds to LAS. One output for each dense and sparse point cloud will be produced.\nIt can be used as follows:\n\n`opf2las path_to/project.opf --out-dir your_output_dir`\n\n#### Convert to PLY\n\nA tool converting an OPF project's point clouds to PLY. One output for each dense and sparse point cloud will be produced.\nIt can be used as follows:\n\n`opf2ply path_to/project.opf --out-dir your_output_dir`\n\n### Examples\n\nWe provide also a few examples of command line scripts to illustrate and educate about various photogrammetry knowledge using the OPF projects.\n\n#### Compute reprojection error\n\nThis script computes the reprojection error of input GCPs in calibrated cameras using the OPF project as an input.\n\n`python examples/compute_reprojection_error.py --opf_path path_to/project.opf`\n\n## License and citation\n\nIf you use this work in your research or projects, we kindly request that you cite it as follows:\n\nThe Open Photogrammetry Format Specification, Gr\u00e9goire Kr\u00e4henb\u00fchl, Klaus Schneider-Zapp, Bastien Dalla Piazza, Juan Hernando, Juan Palacios, Massimiliano Bellomo, Mohamed-Gha\u00efth Kaabi, Christoph Strecha, Pix4D, 2023, retrieved from https://pix4d.github.io/opf-spec/\n\nCopyright (c) 2023 Pix4D SA\n\nAll scripts and/or code contained in this repository are licensed under Apache License 2.0.\n\nThird party documents or tools that are used or referred to in this specification are licensed under their own terms by their respective copyright owners.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Python library for I/O and manipulation of projects under the Open Photogrammetry Format (OPF)",
"version": "1.4.0",
"project_urls": {
"Homepage": "https://pix4d.github.io/opf-spec/specification/project.html"
},
"split_keywords": [
"photogrammetry",
" opf"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "411e4fdfb0059714b05bc8995aac8c57a73915e0498da28095fae2b29032b329",
"md5": "fbc30fb06147b78ba4130e4f6d7b6f83",
"sha256": "054da83531eb3fa0b04fb1630825ad5285ca58181932367da81f7d6c048cfa3b"
},
"downloads": -1,
"filename": "pyopf-1.4.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "fbc30fb06147b78ba4130e4f6d7b6f83",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 96044,
"upload_time": "2024-12-12T06:51:35",
"upload_time_iso_8601": "2024-12-12T06:51:35.821902Z",
"url": "https://files.pythonhosted.org/packages/41/1e/4fdfb0059714b05bc8995aac8c57a73915e0498da28095fae2b29032b329/pyopf-1.4.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "b86066dd38d2bf89fd102d2e366a317c16e754ab0bd3c1a869c81aee44d79751",
"md5": "815f20017f2eb48bc3feea1a6de0b95b",
"sha256": "5a2ee98dfd304c5156f3e1535d11b5779434bbee2ea0ccddd4f688f9b4ff6a12"
},
"downloads": -1,
"filename": "pyopf-1.4.0.tar.gz",
"has_sig": false,
"md5_digest": "815f20017f2eb48bc3feea1a6de0b95b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 71652,
"upload_time": "2024-12-12T06:51:37",
"upload_time_iso_8601": "2024-12-12T06:51:37.102737Z",
"url": "https://files.pythonhosted.org/packages/b8/60/66dd38d2bf89fd102d2e366a317c16e754ab0bd3c1a869c81aee44d79751/pyopf-1.4.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-12 06:51:37",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "pyopf"
}