-
Notifications
You must be signed in to change notification settings - Fork 1.1k
ODM Architecture Overview
ODM is just a sequence of tools (thus the "toolkit" reference in the description of the project). Think of it as a sequence of tasks that have to be executed in a certain order to obtain the results that we want. ODM itself doesn't do anything too fancy. What it's good at however, is at linking together various applications that perform various photogrammetry and geospatial tasks to obtain the results that we want, from pictures to orthophotos, 3D models, elevation models, etc.
Since we mentioned that ODM is a sequence of tasks, we'll start from here. One of the early developers of ODM was familiar with robotics, so when it came to choosing a library to perform sequence of tasks, the choice (for the good and the bad) fell on ecto (https://plasmodic.github.io/ecto/ecto/overview/index.html), which is a library to do just that, execute tasks in a sequence. The library borrows names from biology, so you'll find that individual tasks are defined within "ecto cells" and connections between cells are dependencies between tasks (a fancy way to say, some tasks need to be executed before others).
A --> B --> C
A gets executed first, then B, then C.
The main "cells" (tasks) in ODM are the ones that derive from ecto.Cell and these are:
Cell | File | Description |
---|---|---|
ODMLoadDatasetCell | scripts/dataset.py | Reads the input images and optional GCP files, creates a "images.json" database, extracts the latitude/longitude information from images (from tags called EXIF). |
ODMMvsTexCell | scripts/mvstex.py | Takes a 3D mesh (a file representing a 3D surface) and adds colors to the model ("textures" in computer graphics speak) so that the model looks realistic. |
ODMDEMCell | scripts/odm_dem.py | Generates 2D images representing elevation models (digital surface model, or digital terrain model) starting from a point cloud that has been georeferenced. |
ODMGeoreferencingCell | scripts/odm_georeferencing.py | When the photogrammetry process is completed, all models are still using a local coordinate system (they are not placed anywhere in the real world, they are in a bogus arbitrarily defined position). In this step we add real world coordinates to 3D models and point clouds. |
ODMeshingCell | scripts/odm_meshing.py | We go from point clouds to 3D models (we connect the dots in the point cloud so that we have a model with a continuous surface). We do this either with a Poisson reconstruction algorithm, or using a 2.5D approach. |
ODMOrthoPhotoCell | scripts/odm_orthophoto.py | To generate a map, we simply look at our 3D model from above, and *snap*, we take a picture of the 3D model which becomes our PNG orhophoto. We then use georeferencing information to add a position to the photo and get a GeoTIFF. |
ODMSlamCell | scripts/odm_slam.py | Converts video to sequence of still photos (it hasn't been used in a while though and is experimental). |
ODMOpenSfMCell | scripts/run_opensfm.py | One of the most important process of the pipeline, this is where we go from images to camera positions and obtain a sparse point cloud (a process referred to as "structure from motion". The photogrammetry process is not over yet however, as we need to "densify" or "extract" more points to get a "dense" point cloud, either via OpenSfM, SMVS or MVE (below) |
ODMSmvsCell | scripts/smvs.py | SMVS approach to go from camera positions + images to dense point clouds "Shading-Aware Multi-View Stereo". |
ODMMveCell | scripts/mve.py | MVE (Multi View Environment), a different but better approach to go from camera positions + images to dense point clouds. |
It's worth noting that even though ecto allows us to define connections (which tasks should the pipeline execute), we often times use a more straightforward approach by simply executing all the tasks (all the cells) in sequential order, and if a tasks should not be executed, we simply have an if statement skipping the execution.
For example, whether the --dsm flag (generate an elevation model) is passed or not, the ODMDEMCell is always executed anyway, but within the cell you'll find code that looks like:
# Do we need to process anything here?
if (args.dsm or args.dtm) and las_model_found:
# continue...
Which skips the computation. We plan to phase out ecto at some point in the near future, so we don't break down all tasks into single cells. For example, even though we have options to process both digital surface models (DSM) and digital terrain models (DTM), we don't have two separate cells (instead they are grouped together in ODMDEMCell).
For example, if you are looking to add a call to scene2pset as part of the MVE workflow, it's probably not worth creating a new cell, and call can be added as part of the ODMMveCell cell.
Ecto has a complex system for defining "inputs" and "outputs". The idea is that a cell can receive different input parameters and will produce certain output values (all very useful in robotics). We mostly however don't have a need for such complicated logic, so if you need to reference a command line argument (is a parameter set?) simply reference the generic "args" object as in:
def declare_io(self, params, inputs, outputs):
inputs.declare("args", "The application arguments.", {})
def process(self, inputs, outputs):
args = self.inputs.args
print(args)
In a similar manner, expect for a few places, we typically have no use for the output value capabilities of ecto. Most stages of the pipeline are independent and rely on the state of what files are available, for example, "does a point cloud file exists?". Thus you will rarely need to define output parameters.
Typically this the order that tasks are executed in ODM.
LoadDataset --> OpenSfM --> MVE or SMVS --> Meshing --> MvsTex --> Georeferencing --> DEM --> Orthophoto
Not every stage executes an action (for example, DEM does something only if --dsm or --dtm is set and a georeferenced point cloud exists).
Directory | Description |
---|---|
SuperBuild | Remember we mentioned ODM just runs a lot of other programs? Well, here's where we define how to build them (and there are a lot of dependencies for each one). Once built, these programs end up in SuperBuild/build For MVE for example, find the SuperBuild/CMakeLists.txt file and there's going to be a reference to externalproject_add(mve). Some larger projects get their own special separate file in SuperBuild/cmake/External-* |
contrib | Community contribution scripts, which are not necessary to run ODM, but community users have found them useful for various pre/post processing operations. |
docker | Support files to build the docker images. Mainly consists of a few scripts that allow us to create architecture (CPU) independent builds of ODM |
docs | GitHub files for contribution guidelines. |
hooks | A git hook file (not sure if it still works or is still relevant) |
img | Screenshots to be posted on GitHub have gone here, although to keep the repo size under control we should probably change habit. |
licenses | References to licenses for third party dependencies are included here |
modules | Remember we mentioned ODM just runs a lot of other programs? Some of these programs have been built specifically for ODM (in C++ for speed reasons) and they are defined here. There are programs to clean meshes, generate orthophotos and do georeferencing tasks. When we build these programs, they end up in build/ |
opendm | Python code that is not related to ecto has been placed here. Most of the modules in here are referenced by ecto. You'll find code to perform various tasks such as file operations, ground sampling distance (GSD) calculations and so forth. We try to keep functions in opendm and then reference them from the ecto cells whenever possible (so that when we migrate away from ecto, things will be a bit easier). When you see an import statement such as: from opendm import config We are referencing the opendm/config.py file. |
patched_files | I don't think this being used anymore. Recently, if we need to patch a program that we depend on, we typically try to send the patch to the project owners (upstream), but sometimes that is not possible, in which case we fork the project under the OpenDroneMap organization and then patch the files in the fork. You'll see that we have forks of several programs under http://github.com/OpenDroneMap/ and we use some of them (you'll see them referenced in the SuperBuild directory files) |
scripts | Ecto cells (tasks) are defined here. All tasks are linked together by odm_app.py. |
tests | Some integration tests are available here, although I'm unsure of their status (do they still work)? Speaking of tests, we have a testing suite available at https://github.com/OpenDroneMap/oats which you might find useful to compare results between versions of ODM. Say for example that you add MVE functionality. After the work is done, you can run the new version and compare results with an older version to see how results have improved! |
Nothing in this code base is set in stone and we welcome contributions of any kind. Whether a simplification, addition of comments, or removal of older/obsolete code parts, if you see something that you think could be improved, you are probably right! Regardless of experience, all contributions, however small, that help bring the software forward are worthy of inclusion in the ODM codebase. We have a code of conduct and contributing guidelines document which lay some ground rules for creating an inclusive collaborative environment.