├── .github └── workflows │ ├── black.yml │ ├── isort.yml │ └── publish-image.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── R ├── normalize_image_intensity.R └── prep_gcps.R ├── README.md ├── automate-metashape.Rproj ├── calibration ├── RP02-1618153-SC.csv ├── RP02-1622152-SC.csv ├── RP04-1806002-SC.csv └── RP04-1923118-OB.csv ├── config └── config-base.yml ├── prior-versions └── metashape_v1.6-1.8 │ ├── R │ ├── prep_configs.R │ └── prep_gcps.R │ ├── config │ ├── base.yml │ ├── derived.yml │ └── example.yml │ └── python │ ├── metashape_workflow.py │ ├── metashape_workflow_functions.py │ └── read_yaml.py ├── python ├── metashape_workflow.py └── metashape_workflow_functions.py └── settings.json /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: Black Linter 2 | 3 | # This allows Black be called from the isort workflow 4 | on: 5 | workflow_call: 6 | 7 | jobs: 8 | black-linter: 9 | name: Run Black 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Determine Branch Name 14 | run: | 15 | if [ "${{ github.event_name }}" = "pull_request" ]; then 16 | echo "BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV 17 | else 18 | BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/}) 19 | echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV 20 | fi 21 | 22 | - name: Checkout code 23 | uses: actions/checkout@v3 24 | with: 25 | ref: ${{ env.BRANCH_NAME }} 26 | 27 | - name: Pull latest changes 28 | run: git pull origin ${{ env.BRANCH_NAME }} 29 | 30 | - name: Run Black formatter 31 | uses: psf/black@stable 32 | with: 33 | options: "--verbose" 34 | src: "." 35 | jupyter: true 36 | 37 | - name: Push changes 38 | uses: stefanzweifel/git-auto-commit-action@v4 39 | with: 40 | commit_message: Apply black formatting changes -------------------------------------------------------------------------------- /.github/workflows/isort.yml: -------------------------------------------------------------------------------- 1 | name: Code Formatter 2 | 3 | # Add 'pull_request:' beneath 'on:' to apply on active PRs 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | isort: 11 | name: Run Isort 12 | runs-on: ubuntu-latest 13 | 14 | # Redundant to determine branch name since workflow only runs on main, but will be useful if we also want the workflow to run PRs 15 | steps: 16 | - name: Determine Branch Name 17 | run: | 18 | if [ "${{ github.event_name }}" = "pull_request" ]; then 19 | echo "BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV 20 | else 21 | BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/}) 22 | echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV 23 | fi 24 | 25 | - name: Checkout code 26 | uses: actions/checkout@v3 27 | with: 28 | ref: ${{ env.BRANCH_NAME }} 29 | 30 | - name: Set up Python 31 | uses: actions/setup-python@v2 32 | with: 33 | python-version: 3.9 34 | 35 | - name: Run Isort 36 | run: pip install isort==5.13.2 && isort . 37 | 38 | - name: Push changes 39 | uses: stefanzweifel/git-auto-commit-action@v5 40 | with: 41 | commit_message: Apply isort formatting changes 42 | 43 | # Ensure Black runs after Isort has completed 44 | black: 45 | needs: isort 46 | uses: ./.github/workflows/black.yml 47 | 48 | # Ensure publish-image runs after Black and Isort have completed 49 | publish-image: 50 | needs: [isort, black] 51 | uses: ./.github/workflows/publish-image.yml -------------------------------------------------------------------------------- /.github/workflows/publish-image.yml: -------------------------------------------------------------------------------- 1 | name: Publish image to Github Packages 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | # Do not run on pushes to main, because this is separately triggred on pushes to main by the 7 | # isort workflow, and we want to make sure it runs after isort, and not twice 8 | - main 9 | workflow_call: 10 | 11 | env: 12 | REGISTRY: ghcr.io 13 | IMAGE_NAME: ${{ github.repository }} 14 | 15 | jobs: 16 | build-and-push-image: 17 | runs-on: ubuntu-latest 18 | 19 | permissions: 20 | contents: read 21 | packages: write 22 | attestations: write 23 | id-token: write 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Log in to the Container registry 30 | uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 31 | with: 32 | registry: ${{ env.REGISTRY }} 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Extract metadata (tags, labels) for Docker 37 | id: meta 38 | uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 39 | with: 40 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 41 | tags: | 42 | # The first 4 lines are default; have to include them in order to specify the 43 | # 5th one (apply latest tag to main branch) 44 | type=schedule 45 | type=ref,event=branch 46 | type=ref,event=tag 47 | type=ref,event=pr 48 | type=raw,value=latest,enable={{is_default_branch}} 49 | 50 | - name: Build and push Docker image 51 | id: push 52 | uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 53 | with: 54 | context: . 55 | push: true 56 | tags: ${{ steps.meta.outputs.tags }} 57 | labels: ${{ steps.meta.outputs.labels }} 58 | 59 | # Diabling attestation for now because it is creating two images in the registry for every 60 | # push and making it look messy 61 | 62 | # - name: Generate artifact attestation 63 | # uses: actions/attest-build-provenance@v1 64 | # with: 65 | # subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} 66 | # subject-digest: ${{ steps.push.outputs.digest }} 67 | # push-to-registry: true 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | metashape.lic 6 | 7 | 8 | # Created by https://www.gitignore.io/api/python 9 | # Edit at https://www.gitignore.io/?templates=python 10 | 11 | ### Python ### 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | pip-wheel-metadata/ 35 | share/python-wheels/ 36 | *.egg-info/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # pipenv 81 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 82 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 83 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 84 | # install all needed dependencies. 85 | #Pipfile.lock 86 | 87 | # celery beat schedule file 88 | celerybeat-schedule 89 | 90 | # SageMath parsed files 91 | *.sage.py 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # Mr Developer 101 | .mr.developer.cfg 102 | .project 103 | .pydevproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | # Pyre type checker 114 | .pyre/ 115 | 116 | # End of https://www.gitignore.io/api/python 117 | 118 | 119 | ## apparently added by pycharm 120 | .idea/ 121 | 122 | ## dev folder for temp configs etc 123 | dev/ 124 | 125 | ## Mac system file 126 | .DS_Store -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a GPU-enabled base image for Ubuntu 24.04 2 | FROM nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04 3 | 4 | USER root 5 | 6 | # Adapted from https://github.com/jeffgillan/agisoft_metashape/blob/main/Dockerfile 7 | LABEL authors="David Russell" 8 | LABEL maintainer="djrussell@ucdavis" 9 | 10 | # Create user account with password-less sudo abilities 11 | RUN useradd -s /bin/bash -g 100 -G sudo -m user 12 | RUN /usr/bin/printf '%s\n%s\n' 'password' 'password'| passwd user 13 | RUN echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 14 | 15 | ENV DEBIAN_FRONTEND=noninteractive 16 | 17 | # Install basic dependencies 18 | RUN apt-get update && \ 19 | apt-get install -y libglib2.0-dev libglib2.0-0 libgl1 libglu1-mesa libcurl4 wget python3-venv python3-full libgomp1 && \ 20 | rm -rf /var/lib/apt/lists/* 21 | 22 | # Download the Metashape .whl file 23 | RUN cd /opt && wget https://download.agisoft.com/Metashape-2.2.0-cp37.cp38.cp39.cp310.cp311-abi3-linux_x86_64.whl 24 | 25 | # Create a virtual environment for Metashape 26 | RUN python3 -m venv /opt/venv_metashape 27 | 28 | # Activate the virtual environment and install Metashape and PyYAML 29 | RUN /opt/venv_metashape/bin/pip install --upgrade pip 30 | RUN /opt/venv_metashape/bin/pip install /opt/Metashape-2.2.0-cp37.cp38.cp39.cp310.cp311-abi3-linux_x86_64.whl 31 | RUN /opt/venv_metashape/bin/pip install PyYAML 32 | 33 | # Remove the downloaded wheel file 34 | RUN rm /opt/*.whl 35 | 36 | # Set the container workdir 37 | WORKDIR /app 38 | # Copy files from current directory into /app 39 | COPY . /app 40 | 41 | # Set the default command and default arguments 42 | ENV PATH="/opt/venv_metashape/bin:${PATH}" 43 | ENTRYPOINT ["python3", "/app/python/metashape_workflow.py"] 44 | CMD ["/data/config.yml"] 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, UC Davis 4 | Authors: Derek Young, Alex Mandel, Mallika Nocco, Robert Hijmans, and contributors 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /R/normalize_image_intensity.R: -------------------------------------------------------------------------------- 1 | # Function to take a directory of images and normalize their intensities, saving the normalized images in a new directory named {input_dir}_normalized 2 | 3 | library(magick) 4 | library(stringr) 5 | library(furrr) 6 | 7 | # Function for a single image, to parallelize across within a directory of images 8 | normalize_one_img = function(img_file, img_dir, out_dir) { 9 | 10 | # get the relative path below the specified folder (for saving in same folder structure within the "..._normalized" folder) 11 | rel_path = str_replace(img_file, pattern = fixed(img_dir), replacement = "") 12 | 13 | out_file = file.path(out_dir, rel_path) 14 | 15 | # if already computed for this image, skip 16 | if(file.exists(out_file)) return(FALSE) 17 | 18 | img = image_read(img_file) 19 | 20 | img_norm = image_normalize(img) 21 | 22 | # create dir if doesn't exist 23 | out_dir_img = dirname(out_file) 24 | if(!dir.exists(out_dir_img)) dir.create(out_dir_img, recursive = TRUE) 25 | 26 | image_write(img_norm, out_file) 27 | 28 | gc() 29 | 30 | } 31 | 32 | 33 | # Function to parallelize across images in a directory 34 | normalize_images_in_dir = function(img_dir) { 35 | 36 | # What string to append to the input directory name to store the normalized images 37 | out_dir = paste0(img_dir, "_normalized") 38 | if(!dir.exists(out_dir)) dir.create(out_dir, recursive=TRUE) 39 | 40 | img_files = list.files(img_dir, pattern = "(JPG|jpg|tif|TIF)$", recursive = TRUE, full.names = TRUE) 41 | 42 | gc() 43 | 44 | plan(multisession) 45 | 46 | future_walk(img_files, normalize_one_img, img_dir = img_dir, out_dir = out_dir) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /R/prep_gcps.R: -------------------------------------------------------------------------------- 1 | ### Author: Derek Young, UC Davis 2 | 3 | ### This script does the following: 4 | ### - Loads a user-created data file of the image files (and coordinates in each image) where GCPs are located 5 | ### - Loads a geospatial file containing the locations of the GCPs 6 | ### - Loads a 10 m DEM and extracts elevation values at each GCP 7 | ### - Compiles all of the above into a file needed by Metashape for its GCP workflow 8 | ### - Produces a PDF that shows each GCP image and the location of the GCP for QAQC 9 | 10 | 11 | ### This script requires the following folders/files in the main mission imagery directory (the one containing 100MEDIA etc): 12 | ### - gcps/raw/gcps.gpkg : geospatial data file with each gcp id in the column "gcp_id". Must be in the same projection as the whole Metashape project, and must be in meters xy (usually a UTM zone). 13 | ### - gcps/raw/gcp_imagecoords.csv : data file listing the images and coordinates in each where a GCP is visible (created manually by technician via imagery inspection) 14 | ### - dem_usgs/dem_usgs.tif : 10 m USGS dem extending well beyond the project flight area 15 | 16 | ### This script assumes all images to be linked to GCPs have standard DJI naming and directory structure ("100MEDIA/DJI_xxxx.JPG"). 17 | ### In input data file gcp_imagecoords.csv, images are specified without "DJI_", leading zeros, and ".JPG". The image directory is specified without "MEDIA" 18 | 19 | 20 | #### Load packages #### 21 | 22 | library(sf) 23 | library(raster) 24 | library(dplyr) 25 | library(stringr) 26 | library(magick) 27 | library(ggplot2) 28 | 29 | #### User-defined vars (only used when running interactivesly) #### 30 | 31 | dir_manual = "/home/derek/Downloads/crater_gcps" 32 | 33 | 34 | 35 | #### Load data #### 36 | 37 | ### All relevant GCP data should be in the top-level mission imagery folder 38 | ### Load folder from the command line argument 39 | 40 | 41 | dir = commandArgs(trailingOnly=TRUE) 42 | 43 | if(length(dir) == 0) { 44 | dir = dir_manual 45 | } 46 | 47 | gcps = read_sf(paste0(dir,"/gcps/raw/gcps.geojson")) 48 | imagecoords = read.csv(paste0(dir,"/gcps/raw/gcp_imagecoords.csv"),header=TRUE,stringsAsFactors=FALSE) 49 | dem_usgs = raster(paste0(dir,"/dem_usgs/dem_usgs.tif")) 50 | 51 | # remove blank lines from image coords file 52 | imagecoords = imagecoords %>% 53 | filter(!is.na(x)) 54 | 55 | 56 | #### Make prepared data directory if it doesn't ecist #### 57 | dir.create(paste0(dir,"/gcps/prepared"),showWarnings=FALSE) 58 | 59 | 60 | 61 | #### Create GCP table in the format required by metashape_control and metashape_functions #### 62 | 63 | # Extract elev 64 | gcp_table = gcps 65 | gcp_table$elev = suppressWarnings(extract(dem_usgs,gcp_table,method="bilinear")) 66 | 67 | # Extract coords 68 | coords = st_coordinates(gcp_table) 69 | gcp_table = cbind(gcp_table,coords) 70 | 71 | # Remove geospatial info 72 | st_geometry(gcp_table) = NULL 73 | 74 | # Reorder columns, add "point" prefix to GCP names 75 | gcp_table = gcp_table %>% 76 | dplyr::select(gcp_id,x=X,y=Y,elev) %>% 77 | dplyr::mutate(gcp_id = paste0("point",gcp_id)) 78 | 79 | write.table(gcp_table,paste0(dir,"/gcps/prepared/gcp_table.csv"),row.names=FALSE,col.names=FALSE,sep=",") 80 | 81 | 82 | #### Create image coordinate-to-gcp table in the format required by metashape_control and metashape_functions #### 83 | 84 | imagecoords_table = imagecoords %>% 85 | mutate(gcp_id = paste0("point",gcp)) %>% 86 | mutate(image_text = paste0("DJI_",str_pad(image_file,4,pad="0"),".JPG")) %>% 87 | mutate(part_text = paste0("PART_",str_pad(part_folder,2,pad="0"))) %>% 88 | mutate(folder_text = paste0(media_folder,"MEDIA")) %>% 89 | mutate(image_path = paste0(part_text,"/",folder_text,"/",image_text)) %>% 90 | dplyr::select(gcp_id,image_path,x,y) %>% 91 | arrange(gcp_id,image_path) 92 | 93 | # remove blank lines from image coords file 94 | imagecoords = imagecoords %>% 95 | filter(!is.na(gcp)) 96 | 97 | 98 | write.table(imagecoords_table,paste0(dir,"/gcps/prepared/gcp_imagecoords_table.csv"),row.names=FALSE,col.names=FALSE,sep=",") 99 | 100 | 101 | #### Export a PDF of images with the GCP circled on each #### 102 | 103 | 104 | pdf(paste0(dir,"/gcps/prepared/gcp_qaqc.pdf")) 105 | 106 | for(i in 1:nrow(imagecoords_table)) { 107 | 108 | imagecoords_row = imagecoords_table[i,] 109 | 110 | img = image_read(paste0(dir,"/",imagecoords_row$image_path)) 111 | img = image_scale(img,"10%") 112 | img = image_flip(img) 113 | 114 | img_x = imagecoords_row$x/10 115 | img_y = imagecoords_row$y/10 116 | img_gcp = imagecoords_row$gcp_id 117 | img_path = imagecoords_row$image_path 118 | 119 | map = image_ggplot(img) + 120 | geom_point(x=img_x,y=img_y,size=20,pch=1,fill=NA,color="red",stroke=1) + 121 | geom_point(x=img_x,y=img_y,size=20,pch=3,fill=NA,color="red",stroke=0.5) + 122 | labs(title=paste0(img_gcp,"\n",img_path)) 123 | 124 | print(map) 125 | 126 | cat("Completed GCP ", i, " of ",nrow(imagecoords_table),"\r") 127 | 128 | } 129 | 130 | garbage = dev.off() 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy, reproducible Metashape workflows 2 | 3 | A simple command line tool to automate end-to-end photogrammetry workflows using [Agisoft Metashape](https://www.agisoft.com/). Metashape is proprietary structure-from-motion photogrammetry software that creates 3D point clouds, digital elevation models, mesh models, and orthomosaics from overlapping aerial imagery. Our no-code automation increases the speed of image product creation and makes your workflows fully reproducible and documented. Run parameters are controlled through a single, intuitive configuration file. We offer a [native installation workflow](#native-installation-workflow) as well as a [docker workflow](#docker-workflow). You need to provide **1.** a Metashape license, **2.** your aerial images, and optionally **3.** [ground control points](#preparing-ground-control-points-gcps). 4 | 5 | 6 |
7 |
8 | 9 | 10 | ## Native Installation Workflow 11 | 12 | ### Download and Install Software 13 | **Python:** You need Python 3.7-3.11. We recommend the [Anaconda distribution](https://www.anaconda.com/distribution/) because it includes all the required libraries. When installing, if asked whether the installer should initialize Anaconda3, say "yes". Anaconda must be initialized upon install such that `python` can be called from the command line. A way to check is to simply enter `python` at your command prompt and see if the resulting header info includes Anaconda and Python 3. If it doesn't, you may still need to initialize your Conda install. **Alternative option:** If you want a minimal python installation (such as if you're installing on a computing cluster), you can install [miniconda](https://docs.conda.io/en/latest/miniconda.html) instead. After intalling miniconda, you will need to install additional packages required by our scripts (currently only `PyYAML`) using `pip install {package_name}`. 14 | 15 | **Reproducible workflow scripts (python):** Simply clone this repository to your machine! `git clone https://github.com/open-forest-observatory/automate-metashape.git` 16 | 17 | **Metashape:** You must install the Metashape Python 3 module (Metashape version 2.0). Download the [current .whl file](https://www.agisoft.com/downloads/installer/) and install it following [these instructions](https://agisoft.freshdesk.com/support/solutions/articles/31000148930-how-to-install-metashape-stand-alone-python-module) (using the name of the .whl file that you downloaded). NOTE: If you wish to use an older version of Metashape (v1.6-1.8), the primary scripts here (for v2.0) are not backwards-compatible, but scripts for older versions (with somewhat more limited configuration options) are archived in `prior-versions/`. For the Metashape v1.6-1.8-compatible scripts, you need Python 3.5-3.7. 18 | 19 | **Metashape license:** You need a license (and associated license file) for Metashape. The easiest way to get the license file (assuming you own a license) is by installing the [Metashape Professional Edition GUI software](https://www.agisoft.com/downloads/installer/) (distinct from the Python module) and registering it following the prompts in the software (note you need to purchase a license first). UC Davis users, inquire over the geospatial listserv or the #spatial Slack channel for information on joining a floating license pool. Once you have a license file (whether a node-locked or floating license), you need to set the `agisoft_LICENSE` environment variable (search onilne for instructions for your OS; look for how to *permanently* set it) to the path to the folder containing the license file (`metashape.lic`). On many Linux systems, assuming the Metashape GUI is installed in `/opt/metashape-pro/`, you can set the environment variable with `export agisoft_LICENSE=/opt/metashape-pro/`, though if run directly in a bash terminal it will only be effective during that bash session. 20 | 21 |
22 | 23 | ** Internal OFO developers only: Python and the Metashape python module are pre-installed and ready for use on 'ofo-dev' instances launched from Exosphere and as well as 'Open-Forest-Observatory' template launched from CACAO. The software is installed in a conda environment. `conda activate meta` 24 | 25 |
26 | 27 | ### Organizing raw imagery (and associated files) for processing 28 | 29 | Images should be organized such that there is one root level that contains all the photos from the flight mission to be processed (these photos may optionally be organized within sub-folders), and no other missions. If your workflow is to include the **optional** inputs of _spectral calibration_, [ground control points (GCPs)](#preparing-ground-control-points-gcps), and/or a _USGS DEM_, this root-level folder *must* also contain a corresponding folder for each. For example: 30 | 31 | ``` 32 | mission001_photos 33 | ├───100MEDIA 34 | | DJI_0001.JPG 35 | | DJI_0002.JPG 36 | | ... 37 | ├───101MEDIA 38 | | DJI_0001.JPG 39 | | DJI_0002.JPG 40 | | ... 41 | ├───102MEDIA 42 | | DJI_0001.JPG 43 | | DJI_0002.JPG 44 | | ... 45 | ├───gcps 46 | | ... 47 | ├───dem_usgs 48 | | dem_usgs.tif 49 | └───calibration 50 | RP04-1923118-OB.csv 51 | ``` 52 | 53 | The namings for the ancillary data folders (`gcps`, `dem_usgs`, and `calibration`) must exactly match these if they are to be a part of the workflow. 54 | 55 | A **sample RGB photo dataset** (which includes GCPs and a USGS DEM) may be [downloaded here](https://ucdavis.box.com/s/hv8m8fibe164pjj0mssdx1mj8qb996k8) (1.5 GB). Note this dataset has sparse photos (low overlap), so photogrammetry results are unimpressive. 56 | 57 |
58 | 59 | ### Workflow configuration 60 | 61 | All of the parameters defining the Metashape workflow are specified in the configuration file (a [YAML-format](https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html) file). This includes directories of input and output files, workflow steps to include, quality settings, and many other parameters. 62 | 63 | An example configuration file is provided in this repo at [`config/config-base.yml`](/config/config-base.yml). Edit the parameter values to meet your specifications. The file contains comments explaining the purpose of each customizable parameter. You can directly edit the `config-base.yml` or save a new copy somewhere on the your local computer. You will specify the path of this config.yml in the [python run command](#running-the-workflow). 64 | 65 | Note: Please do not remove or add parameters to the configuration file; adding will have no effect unless the Python code is changed along with the addition, and removing will produce errors. 66 | 67 |
68 | 69 | ### Running the Workflow 70 | 71 | The general command line call to run the workflow has two required components: 72 | 1. Call to Python 73 | 2. Path to metashape workflow Python script (`metashape_workflow.py`) 74 | 75 | For example: 76 | 77 | `python {repo_path}/python/metashape_workflow.py` 78 | 79 |
80 | 81 | With this minimalist run command, the script assumes your config.yml file is located in the repo at `{repo_path}/config/config-base.yml` 82 | 83 |
84 | 85 | If your config file is located in a different directory, use the optional flag `--config_file` 86 | 87 | For example: 88 | 89 | `python {repo_path}/python/metashape_workflow.py --config_file {config_path}/{config_file}.yml` 90 | 91 | 92 |
93 | 94 | **Additional run command flags**. Using these flags will override parameters specified in the config.yml file. 95 | 96 | `--photo-path` Path to the directory that contains the aerial images (usually jpegs) to be processed 97 | 98 | `--photo-path-secondary` Path to the directory that contains aerial images which are aligned only after all other processing is done. Not commonly used. 99 | 100 | `--project-path` Path where the metashape project file (.psx) will be written 101 | 102 | `--output-path` Path where all imagery products (orthomosaic, point cloud, DEMs, mesh model, reports) will be written 103 | 104 | `--run-name` The identifier for the run. Will be used in naming output files 105 | 106 | `--project-crs` Coordinate reference system EPSG code for outputs. Eg. _EPSG:26910_ 107 | 108 |
109 | 110 |
111 | 112 | 113 | #### Running workflow batches 114 | 115 | Running workflows in batch (i.e., multiple workflows in series) on a single computer is as simple as creating configuration file for each workflow run and calling the Python workflow script once for each. The calls can be combined into a shell script. Here is a quick workflow of how to do this: 116 | 117 | 1. Create an empty shell script `touch metashape.sh` 118 | 2. Populate the script with run commands on different lines. Note: the only thing that changes is the name of the config file. 119 | 120 | 121 | ``` 122 | python ~/repos/automate-metashape/python/metashape_workflow.py --config_file ~/projects/forest_structure/metashape_configs/config001.yml 123 | python ~/repos/automate-metashape/python/metashape_workflow.py --config_file ~/projects/forest_structure/metashape_configs/config002.yml 124 | python ~/repos/automate-metashape/python/metashape_workflow.py --config_file ~/projects/forest_structure/metashape_configs/config003.yml 125 | ``` 126 | 3. Give the shell script file executable permissions ` chmod +x metashape.sh` 127 | 4. Make sure you are located in the directory that contains the shell script. Run the shell script `./metashape.sh` 128 | 129 | 130 |
131 | 132 | ### Workflow outputs 133 | 134 | The outputs of the workflow are the following: 135 | - **Photogrammetry outputs** (e.g., dense point cloud, orthomosaic, digital surface model, and Metashape processing report) 136 | - **A Metashape project file** (for additional future processing or for inspecting the data via the Metashape GUI) 137 | - **A processing log** (which records the processing time for each step and the full set of configuration parameters, for reproducibility) 138 | 139 | The outputs for a given workflow run are named using the following convention: `{run_name}_abc.xyz`. For example: `set14-highQuality_ortho.tif`. The run name and output directories are specified in the configuration file. 140 | 141 |
142 |
143 |
144 | 145 | --- 146 | 147 |
148 | 149 | ## Docker Workflow 150 | 151 | Docker, a type of software containerization, is an alternative way to run software where you don't need to install software in the traditional sense. Docker packages up the code and all its environment dependencies so the application runs reliably from one computer to another. Background information on docker and software containers can be found [here](https://foss.cyverse.org/07_reproducibility_II/). 152 | 153 | To run a docker container on your local machine, you do need to install `docker`. You can install and run docker as a command line tool for [linux distributions](https://docs.docker.com/engine/install/) or as a graphical program (i.e, Docker Desktop) for [windows](https://docs.docker.com/desktop/setup/install/windows-install/), [macOS](https://docs.docker.com/desktop/setup/install/mac-install/), or [linux](https://docs.docker.com/desktop/setup/install/linux/). We recommend running docker commands at the terminal. If you are using Docker Desktop, you can still write commands at the terminal while Docker Desktop is open and running. 154 | 155 | The `automate-metashape` docker image contains the python libraries needed to run the workflow, while you (the user) need to provide at minimum the **1.** aerial images; **2** a configuration file specifying your choices for processing; **3.** a license to use Metashape; and optionally **4.** [ground control points (GCPs)](#preparing-ground-control-points-gcps). 156 | 157 |
158 | 159 | ### User inputs to docker workflow 160 | 161 | To provide the input data to Metashape, you need to specify a folder from your computer to be mirrored ("mounted") within the Docker container. The files needed to run Metashape (the folder of aerial images, the configuration file, and optionally the GCPs file) must all be in the folder that you mount. The files can be located in any arbitrary directory beneath this folder. For example, if the folder you mount is `~/drone_data` on your local computer, the images could be located at `~/drone_data/images/` and the config file could be located at `~/drone_data/config.yml` The folder from your local machine is mounted into the Docker container at the path `/data/`, so for the example above, the images would be found inside the docker container at the path `/data/images/`. 162 | 163 |
164 | 165 | #### Image directory 166 | 167 | The images to be processed should all be in one parent folder (and optionally organized into subfolders beneath this folder) somewhere within the data folder you will be mounting. If including GCPs, spectral calibratation, and/or the USGS DEM, follow the organization shown [here](#organizing-raw-imagery-and-associated-files-for-processing). 168 | 169 |
170 | 171 | #### Workflow configuration file 172 | 173 | An example configuration file is provided in this repository at [`config/config-base.yml`](/config/config-base.yml). Please download this file to your local machine and rename it `config.yml`. By default the container expects the config YAML file describing the Metashape workflow parameters to be located at `/data/config.yaml`. So in a case where the local folder to be mounted is `~/drone_data/`, then for the config file to be mounted in the Docker container at `/data/config.yaml`, it must be located on the local computer at `~/drone_data/config.yaml`. However, the config file location can be overridden by passing a different location following an optional command line argument `--config_file` of the `docker run` command. For more information click [here](#custom-location-of-the-metashape-configuration-file). 174 | 175 | Within the `config.yml` you will need to edit some of the project level parameters to specify where to find input images and where to put output products within the container. Within this config file, all paths will be relative to the file structure of the docker container (beginning with `/data/`). In the config.yaml, at a minimum the following entries should be updated: 176 | 177 | * The value for 'photo_path' should be updated to `/data/{path_to_images_folder_within_mounted_folder}` 178 | * The value for 'output_path' should be updated to `/data/{path_to_desired_ouputs_folder_within_mounted_folder}` (can be any location you want; will be created if it does not exist) 179 | * The value for 'project_path' should be updated similarly as for 'output_path'. 180 | 181 |
182 | 183 | ### Metashape license 184 | Users need to provide a license to use Metashape. Currently, this docker method only supports a floating license server using the format `:`. Within a terminal, users can declare the floating license as an environment variable using the command: 185 | 186 | `export AGISOFT_FLS=:` 187 | 188 | Keep in mind that environment variables will not persist across different terminal sessions. 189 | 190 |
191 | 192 | ### Enable GPUs for accelerated processing 193 | 194 | The use of graphical processing units (GPUs) can greatly increase the speed of photogrammetry processing. If your machine has GPU hardware, you will need extra software so docker can find and use your GPUs. Linux users simply need to install nvidia-container-toolkit via `sudo apt install nvidia-container-toolkit`. For Windows users please see [this documentation](https://docs.docker.com/desktop/features/gpu/). For macOS user, it may not be possible to use your local GPU (Apple Silicon) through Docker. 195 | 196 |
197 | 198 | ### Run the docker container 199 | From a terminal, run this command: 200 | 201 | `docker run -v :/data -e AGISOFT_FLS=$AGISOFT_FLS --gpus all ghcr.io/open-forest-observatory/automate-metashape` 202 | 203 | Here is a breakdown of the command: 204 | 205 | `docker run` is the command to run a docker image 206 | 207 | `-v :/data` is mounting a volume from your local computer into the container. We are mounting your directory that has the imagery and config file () into the container at the path "/data". 208 | 209 | `-e AGISOFT_FLS=$AGISOFT_FLS` is declaring your floating license to use Metashape. We set the license info as an environmental variable earlier in these instructions (i.e., `export AGISOFT_FLS=:`) 210 | 211 | `--gpus all` If the container has access to your local GPUs, use this flag to enable it. 212 | 213 | `ghcr.io/open-forest-observatory/automate-metashape` This is the docker image that has the software to run the `automate-metashape` script. It is located in the Github container registry. When you execute the `docker run...` command, it will download the container image to your local machine and start the script to process imagery using Metashape. 214 | 215 |
216 | 217 | #### Custom location of the Metashape configuration file 218 | 219 | If your config.yaml is located anywhere other than `/data/config.yaml` (or the file is named differently), you can specify its location following one additional command line argument `--config_file` at the end of the `docker run` command. For example, if it is located in the container at `/data/configs/project_10/config.yml` (meanining, in the example above, it is located on your local computer at `~/drone_data/configs/project_10/config.yml`), just append `--config_file /data/configs/project_10/config.yml` to the `docker run` command above. So the command above would look like: 220 | 221 | `docker run -v :/data -e AGISOFT_FLS=$AGISOFT_FLS --gpus all ghcr.io/open-forest-observatory/automate-metashape --config_file /data/configs/project_10/config.yml` 222 | 223 |
224 | 225 | ### Outputs 226 | 227 | As the processing runs, the completed imagery products will be saved into the folder you specified for the `output_path` parameter in the config.yaml. In the example above, if your config.yaml specifies the `output_path` as `/data/{path_to_desired_ouputs_folder_within_mounted_folder}`, the outputs will be saved on your local computer at `~/drone_data/{path_to_desired_ouputs_folder_within_mounted_folder}`. 228 | 229 |
230 | 231 | ### Permissions on Linux 232 | 233 | If running Docker on Linux without `sudo` (as in this example), your user will need to be in the `docker` group. This can be achieved with `sudo usermod -a -G docker $USER` and then logging out and in, as explained [here](https://docs.docker.com/engine/install/linux-postinstall/). 234 | 235 | Note that the owner of the output data will be the `root` user. To set the ownership to your user account, you can run `sudo chown : ` or `sudo chown : -R `. 236 | 237 | 238 |
239 |
240 | 241 | --- 242 | 243 |
244 | 245 | ## Preparing ground-control points (GCPs) 246 | 247 | Because the workflow implemented here is completely GUI-free, it is necessary to prepare GCPs in advance. The process of preparing GCPs involves recording (a) the geospatial location of the GCPs on the earth and (b) the location of the GCPs within the photos in which they appear. 248 | 249 | Metashape requires this information in a very specific format, so this repository includes an R script to assist in producing the necessary files based on more human-readable input. The helper script is `R/prep_gcps.R`. 250 | 251 | **GCP processing input files.** Example GCP input files are included in the [example RGB photo dataset](https://ucdavis.box.com/s/hv8m8fibe164pjj0mssdx1mj8qb996k8) under `gcps/raw/`. The files are the following: 252 | - **gcps.gpkg**: A geopackage (shapefile-like GIS format) containing the locations of each GCP on the earth. Must include an integer column called `gcp_id` that gives each GCP a unique integer ID number. 253 | - **gcp_imagecoords.csv**: A CSV table identifying the locations of the GCPs within raw drone images. Each GCP should be located in at least 5 images (ideally more). The tabls must contain the following columns: 254 | - `gcp`: the integer ID number of the GCP (to match the ID number in `gcps.gpkg`) 255 | - `folder`: the *integer* number of the subfolder in which the raw drone image is located. For example, if the image is in `100MEDIA`, the value that should be recorded is `100`. 256 | - `image`: the *ingeter* number of the image in which the GCP is to be identified. For example, if the image is named `DJI_0077.JPG`, the value that should be recorded is `77`. 257 | - `x` and `y`: the coordinates of the pixel in the image where the GCP is located. `x` and `y` are in units of pixels right and down (respectively) from the upper-left corner. 258 | 259 | These two files must be in `gcps/raw/` at the top level of the flight mission directory (where the subfolders of images also reside). Identification of the image pixel coordinates where the GCPs are located is easy using the info tool in QGIS. 260 | 261 | **Running the script.** You must have R and the following packages installed: sf, raster, dplyr, stringr, magick, ggplot2. The R `bin` directory must be in your system path, or you'll need to use the full path to R. You run the script from the command line by calling `Rscript --vanilla` with the helper script and passing the location of the top-level mission imagery folder (which contains the `gcp` folder) as an argument. For example, on Windows: 262 | 263 | ``` 264 | Rscript --vanilla {path_to_repo}/R/prep_gcps.R {path_to_imagery_storage}/sample_rgb_photoset 265 | ``` 266 | 267 | **Outputs.** The script will create a `prepared` directory within the `gcps` folder containing the two files used by Metashape: `gcp_table.csv`, which contains the geospatial coordinates of the GCPs on the earth, and `gcp_imagecoords_table.csv`, which contains the pixel coordinates of the GCPs within each image. It also outputs a PDF called `gcp_qaqc.pdf`, which shows the specified location of each GCP in each image in order to quality-control the location data. If left in this folder structure (`gcps/prepared`), the Metashape workflow script will be able to find and incorporate the GCP data if GCPs are enabled in the configuration file. 268 | 269 |
270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /automate-metashape.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /calibration/RP02-1618153-SC.csv: -------------------------------------------------------------------------------- 1 | 250.0,0.0 2 | 251.0,0.0 3 | 252.0,0.0 4 | 253.0,0.0 5 | 254.0,0.0 6 | 255.0,0.0 7 | 256.0,0.0 8 | 257.0,0.0 9 | 258.0,0.0 10 | 259.0,0.0 11 | 260.0,0.0 12 | 261.0,0.0 13 | 262.0,0.0 14 | 263.0,0.0 15 | 264.0,0.0 16 | 265.0,0.0 17 | 266.0,0.0 18 | 267.0,0.0 19 | 268.0,0.0 20 | 269.0,0.0 21 | 270.0,0.0 22 | 271.0,0.0 23 | 272.0,0.0 24 | 273.0,0.0 25 | 274.0,0.0 26 | 275.0,0.0 27 | 276.0,0.0 28 | 277.0,0.0 29 | 278.0,0.0 30 | 279.0,0.0 31 | 280.0,0.0 32 | 281.0,0.0 33 | 282.0,0.0 34 | 283.0,0.0 35 | 284.0,0.0 36 | 285.0,0.0 37 | 286.0,0.0 38 | 287.0,0.0 39 | 288.0,0.0 40 | 289.0,0.0 41 | 290.0,0.0 42 | 291.0,0.0 43 | 292.0,0.0 44 | 293.0,0.0 45 | 294.0,0.0 46 | 295.0,0.0 47 | 296.0,0.0 48 | 297.0,0.0 49 | 298.0,0.0 50 | 299.0,0.0 51 | 300.0,0.0 52 | 301.0,0.0 53 | 302.0,0.0 54 | 303.0,0.0 55 | 304.0,0.0 56 | 305.0,0.0 57 | 306.0,0.0 58 | 307.0,0.0 59 | 308.0,0.0 60 | 309.0,0.0 61 | 310.0,0.0 62 | 311.0,0.0 63 | 312.0,0.0 64 | 313.0,0.0 65 | 314.0,0.0 66 | 315.0,0.0 67 | 316.0,0.0 68 | 317.0,0.0 69 | 318.0,0.0 70 | 319.0,0.0 71 | 320.0,0.0 72 | 321.0,0.0 73 | 322.0,0.0 74 | 323.0,0.0 75 | 324.0,0.0 76 | 325.0,0.0 77 | 326.0,0.0 78 | 327.0,0.0 79 | 328.0,0.0 80 | 329.0,0.0 81 | 330.0,0.0 82 | 331.0,0.0 83 | 332.0,0.0 84 | 333.0,0.0 85 | 334.0,0.0 86 | 335.0,0.0 87 | 336.0,0.0 88 | 337.0,0.0 89 | 338.0,0.0 90 | 339.0,0.0 91 | 340.0,0.0 92 | 341.0,0.0 93 | 342.0,0.0 94 | 343.0,0.0 95 | 344.0,0.0 96 | 345.0,0.0 97 | 346.0,0.0 98 | 347.0,0.0 99 | 348.0,0.0 100 | 349.0,0.0 101 | 350.0,0.0 102 | 351.0,0.0 103 | 352.0,0.0 104 | 353.0,0.0 105 | 354.0,0.0 106 | 355.0,0.0 107 | 356.0,0.0 108 | 357.0,0.0 109 | 358.0,0.0237 110 | 359.0,0.183 111 | 360.0,0.4767 112 | 361.0,0.6355 113 | 362.0,0.6658 114 | 363.0,0.6776 115 | 364.0,0.689 116 | 365.0,0.6958 117 | 366.0,0.7012 118 | 367.0,0.6951 119 | 368.0,0.6859 120 | 369.0,0.6856 121 | 370.0,0.6872 122 | 371.0,0.6884 123 | 372.0,0.6887 124 | 373.0,0.6902 125 | 374.0,0.6914 126 | 375.0,0.6883 127 | 376.0,0.686 128 | 377.0,0.6823 129 | 378.0,0.6773 130 | 379.0,0.6801 131 | 380.0,0.6875 132 | 381.0,0.6845 133 | 382.0,0.6792 134 | 383.0,0.6802 135 | 384.0,0.6812 136 | 385.0,0.6812 137 | 386.0,0.6814 138 | 387.0,0.6812 139 | 388.0,0.6817 140 | 389.0,0.6846 141 | 390.0,0.6843 142 | 391.0,0.6798 143 | 392.0,0.6767 144 | 393.0,0.6765 145 | 394.0,0.6791 146 | 395.0,0.684 147 | 396.0,0.6879 148 | 397.0,0.6885 149 | 398.0,0.6858 150 | 399.0,0.6833 151 | 400.0,0.6833 152 | 401.0,0.6838 153 | 402.0,0.6854 154 | 403.0,0.6866 155 | 404.0,0.6883 156 | 405.0,0.6907 157 | 406.0,0.6925 158 | 407.0,0.6933 159 | 408.0,0.6935 160 | 409.0,0.6942 161 | 410.0,0.6955 162 | 411.0,0.6975 163 | 412.0,0.6982 164 | 413.0,0.6989 165 | 414.0,0.699 166 | 415.0,0.6991 167 | 416.0,0.6999 168 | 417.0,0.7021 169 | 418.0,0.705 170 | 419.0,0.7068 171 | 420.0,0.7065 172 | 421.0,0.7039 173 | 422.0,0.7024 174 | 423.0,0.703 175 | 424.0,0.7035 176 | 425.0,0.7047 177 | 426.0,0.7068 178 | 427.0,0.7078 179 | 428.0,0.7083 180 | 429.0,0.709 181 | 430.0,0.7086 182 | 431.0,0.707 183 | 432.0,0.7049 184 | 433.0,0.7039 185 | 434.0,0.7036 186 | 435.0,0.7037 187 | 436.0,0.7048 188 | 437.0,0.7055 189 | 438.0,0.7044 190 | 439.0,0.7037 191 | 440.0,0.7034 192 | 441.0,0.7028 193 | 442.0,0.7018 194 | 443.0,0.7016 195 | 444.0,0.7033 196 | 445.0,0.7041 197 | 446.0,0.7031 198 | 447.0,0.7023 199 | 448.0,0.7023 200 | 449.0,0.7032 201 | 450.0,0.7033 202 | 451.0,0.7027 203 | 452.0,0.7029 204 | 453.0,0.7033 205 | 454.0,0.7024 206 | 455.0,0.7011 207 | 456.0,0.7012 208 | 457.0,0.702 209 | 458.0,0.7022 210 | 459.0,0.7028 211 | 460.0,0.7031 212 | 461.0,0.7029 213 | 462.0,0.7028 214 | 463.0,0.7029 215 | 464.0,0.7028 216 | 465.0,0.703 217 | 466.0,0.7031 218 | 467.0,0.7029 219 | 468.0,0.703 220 | 469.0,0.703 221 | 470.0,0.7027 222 | 471.0,0.7026 223 | 472.0,0.7024 224 | 473.0,0.7028 225 | 474.0,0.7038 226 | 475.0,0.7042 227 | 476.0,0.7043 228 | 477.0,0.7042 229 | 478.0,0.7038 230 | 479.0,0.7039 231 | 480.0,0.7048 232 | 481.0,0.7053 233 | 482.0,0.7051 234 | 483.0,0.7049 235 | 484.0,0.705 236 | 485.0,0.7054 237 | 486.0,0.7057 238 | 487.0,0.7058 239 | 488.0,0.7059 240 | 489.0,0.7059 241 | 490.0,0.706 242 | 491.0,0.7064 243 | 492.0,0.7073 244 | 493.0,0.7076 245 | 494.0,0.707 246 | 495.0,0.7069 247 | 496.0,0.7072 248 | 497.0,0.7074 249 | 498.0,0.7076 250 | 499.0,0.7073 251 | 500.0,0.707 252 | 501.0,0.7071 253 | 502.0,0.7079 254 | 503.0,0.7086 255 | 504.0,0.7084 256 | 505.0,0.7073 257 | 506.0,0.7067 258 | 507.0,0.7069 259 | 508.0,0.7071 260 | 509.0,0.7077 261 | 510.0,0.7085 262 | 511.0,0.7089 263 | 512.0,0.7097 264 | 513.0,0.7115 265 | 514.0,0.7121 266 | 515.0,0.711 267 | 516.0,0.7104 268 | 517.0,0.7108 269 | 518.0,0.7105 270 | 519.0,0.7107 271 | 520.0,0.712 272 | 521.0,0.7129 273 | 522.0,0.713 274 | 523.0,0.7123 275 | 524.0,0.7114 276 | 525.0,0.7117 277 | 526.0,0.7125 278 | 527.0,0.713 279 | 528.0,0.7132 280 | 529.0,0.7133 281 | 530.0,0.7137 282 | 531.0,0.7138 283 | 532.0,0.7143 284 | 533.0,0.7149 285 | 534.0,0.7152 286 | 535.0,0.7155 287 | 536.0,0.7154 288 | 537.0,0.7154 289 | 538.0,0.7157 290 | 539.0,0.7162 291 | 540.0,0.7158 292 | 541.0,0.7164 293 | 542.0,0.7173 294 | 543.0,0.7172 295 | 544.0,0.7169 296 | 545.0,0.717 297 | 546.0,0.7172 298 | 547.0,0.7165 299 | 548.0,0.7156 300 | 549.0,0.7153 301 | 550.0,0.7155 302 | 551.0,0.7162 303 | 552.0,0.7172 304 | 553.0,0.718 305 | 554.0,0.7178 306 | 555.0,0.7172 307 | 556.0,0.7168 308 | 557.0,0.7172 309 | 558.0,0.7175 310 | 559.0,0.7175 311 | 560.0,0.7175 312 | 561.0,0.7179 313 | 562.0,0.7184 314 | 563.0,0.7181 315 | 564.0,0.7175 316 | 565.0,0.7168 317 | 566.0,0.7172 318 | 567.0,0.7187 319 | 568.0,0.7191 320 | 569.0,0.7182 321 | 570.0,0.7176 322 | 571.0,0.7173 323 | 572.0,0.7171 324 | 573.0,0.7179 325 | 574.0,0.7187 326 | 575.0,0.7192 327 | 576.0,0.7196 328 | 577.0,0.7194 329 | 578.0,0.7188 330 | 579.0,0.719 331 | 580.0,0.72 332 | 581.0,0.7204 333 | 582.0,0.72 334 | 583.0,0.7192 335 | 584.0,0.7189 336 | 585.0,0.7185 337 | 586.0,0.7175 338 | 587.0,0.7173 339 | 588.0,0.7177 340 | 589.0,0.718 341 | 590.0,0.7191 342 | 591.0,0.7201 343 | 592.0,0.7209 344 | 593.0,0.7214 345 | 594.0,0.722 346 | 595.0,0.7216 347 | 596.0,0.7207 348 | 597.0,0.7205 349 | 598.0,0.7202 350 | 599.0,0.7202 351 | 600.0,0.7209 352 | 601.0,0.7213 353 | 602.0,0.7209 354 | 603.0,0.7204 355 | 604.0,0.7205 356 | 605.0,0.7213 357 | 606.0,0.7214 358 | 607.0,0.7203 359 | 608.0,0.7195 360 | 609.0,0.7198 361 | 610.0,0.7208 362 | 611.0,0.7215 363 | 612.0,0.722 364 | 613.0,0.7218 365 | 614.0,0.7213 366 | 615.0,0.7219 367 | 616.0,0.7218 368 | 617.0,0.7205 369 | 618.0,0.7197 370 | 619.0,0.7197 371 | 620.0,0.7204 372 | 621.0,0.7214 373 | 622.0,0.7219 374 | 623.0,0.7221 375 | 624.0,0.722 376 | 625.0,0.7218 377 | 626.0,0.722 378 | 627.0,0.7226 379 | 628.0,0.723 380 | 629.0,0.7226 381 | 630.0,0.7214 382 | 631.0,0.7202 383 | 632.0,0.7206 384 | 633.0,0.7218 385 | 634.0,0.7227 386 | 635.0,0.7229 387 | 636.0,0.7217 388 | 637.0,0.7203 389 | 638.0,0.72 390 | 639.0,0.7204 391 | 640.0,0.7214 392 | 641.0,0.7225 393 | 642.0,0.7224 394 | 643.0,0.721 395 | 644.0,0.72 396 | 645.0,0.7201 397 | 646.0,0.7207 398 | 647.0,0.7211 399 | 648.0,0.7211 400 | 649.0,0.7205 401 | 650.0,0.72 402 | 651.0,0.7202 403 | 652.0,0.7201 404 | 653.0,0.7205 405 | 654.0,0.721 406 | 655.0,0.7207 407 | 656.0,0.7203 408 | 657.0,0.7202 409 | 658.0,0.72 410 | 659.0,0.7198 411 | 660.0,0.7194 412 | 661.0,0.7186 413 | 662.0,0.7175 414 | 663.0,0.7162 415 | 664.0,0.7148 416 | 665.0,0.7148 417 | 666.0,0.7157 418 | 667.0,0.7161 419 | 668.0,0.7154 420 | 669.0,0.7142 421 | 670.0,0.7138 422 | 671.0,0.714 423 | 672.0,0.7133 424 | 673.0,0.7119 425 | 674.0,0.7119 426 | 675.0,0.713 427 | 676.0,0.7136 428 | 677.0,0.7139 429 | 678.0,0.7135 430 | 679.0,0.712 431 | 680.0,0.7106 432 | 681.0,0.71 433 | 682.0,0.7094 434 | 683.0,0.7087 435 | 684.0,0.7087 436 | 685.0,0.7093 437 | 686.0,0.7101 438 | 687.0,0.7103 439 | 688.0,0.7099 440 | 689.0,0.7089 441 | 690.0,0.7078 442 | 691.0,0.708 443 | 692.0,0.7083 444 | 693.0,0.7086 445 | 694.0,0.7091 446 | 695.0,0.7093 447 | 696.0,0.7092 448 | 697.0,0.709 449 | 698.0,0.7091 450 | 699.0,0.7088 451 | 700.0,0.7081 452 | 701.0,0.7083 453 | 702.0,0.7088 454 | 703.0,0.7088 455 | 704.0,0.7078 456 | 705.0,0.7063 457 | 706.0,0.7057 458 | 707.0,0.7059 459 | 708.0,0.7062 460 | 709.0,0.7068 461 | 710.0,0.7074 462 | 711.0,0.7074 463 | 712.0,0.7067 464 | 713.0,0.7063 465 | 714.0,0.7063 466 | 715.0,0.7063 467 | 716.0,0.706 468 | 717.0,0.7057 469 | 718.0,0.7058 470 | 719.0,0.7059 471 | 720.0,0.7057 472 | 721.0,0.7054 473 | 722.0,0.7051 474 | 723.0,0.7048 475 | 724.0,0.7053 476 | 725.0,0.7056 477 | 726.0,0.7049 478 | 727.0,0.7048 479 | 728.0,0.7052 480 | 729.0,0.705 481 | 730.0,0.7049 482 | 731.0,0.7047 483 | 732.0,0.7045 484 | 733.0,0.7042 485 | 734.0,0.7037 486 | 735.0,0.7024 487 | 736.0,0.7016 488 | 737.0,0.7011 489 | 738.0,0.7003 490 | 739.0,0.6994 491 | 740.0,0.6983 492 | 741.0,0.6978 493 | 742.0,0.6985 494 | 743.0,0.6992 495 | 744.0,0.6994 496 | 745.0,0.6996 497 | 746.0,0.6997 498 | 747.0,0.6991 499 | 748.0,0.6982 500 | 749.0,0.6965 501 | 750.0,0.6953 502 | 751.0,0.6955 503 | 752.0,0.6952 504 | 753.0,0.6939 505 | 754.0,0.6934 506 | 755.0,0.6944 507 | 756.0,0.6942 508 | 757.0,0.6934 509 | 758.0,0.6927 510 | 759.0,0.6925 511 | 760.0,0.6925 512 | 761.0,0.692 513 | 762.0,0.691 514 | 763.0,0.6898 515 | 764.0,0.6889 516 | 765.0,0.6879 517 | 766.0,0.6872 518 | 767.0,0.6865 519 | 768.0,0.6859 520 | 769.0,0.6862 521 | 770.0,0.6864 522 | 771.0,0.6861 523 | 772.0,0.6851 524 | 773.0,0.6849 525 | 774.0,0.685 526 | 775.0,0.6844 527 | 776.0,0.6836 528 | 777.0,0.6817 529 | 778.0,0.68 530 | 779.0,0.6799 531 | 780.0,0.6801 532 | 781.0,0.6793 533 | 782.0,0.678 534 | 783.0,0.6774 535 | 784.0,0.6772 536 | 785.0,0.6774 537 | 786.0,0.6777 538 | 787.0,0.6781 539 | 788.0,0.6772 540 | 789.0,0.6765 541 | 790.0,0.6772 542 | 791.0,0.6772 543 | 792.0,0.676 544 | 793.0,0.674 545 | 794.0,0.6734 546 | 795.0,0.6743 547 | 796.0,0.6748 548 | 797.0,0.674 549 | 798.0,0.6723 550 | 799.0,0.6707 551 | 800.0,0.6701 552 | 801.0,0.6697 553 | 802.0,0.6691 554 | 803.0,0.6694 555 | 804.0,0.6704 556 | 805.0,0.6706 557 | 806.0,0.6698 558 | 807.0,0.6692 559 | 808.0,0.6698 560 | 809.0,0.6712 561 | 810.0,0.6712 562 | 811.0,0.6689 563 | 812.0,0.6657 564 | 813.0,0.6645 565 | 814.0,0.6653 566 | 815.0,0.6674 567 | 816.0,0.668 568 | 817.0,0.6665 569 | 818.0,0.6653 570 | 819.0,0.6654 571 | 820.0,0.6655 572 | 821.0,0.6655 573 | 822.0,0.6666 574 | 823.0,0.667 575 | 824.0,0.665 576 | 825.0,0.6634 577 | 826.0,0.6646 578 | 827.0,0.665 579 | 828.0,0.6644 580 | 829.0,0.6645 581 | 830.0,0.6637 582 | 831.0,0.6631 583 | 832.0,0.6645 584 | 833.0,0.6638 585 | 834.0,0.6612 586 | 835.0,0.6611 587 | 836.0,0.662 588 | 837.0,0.6623 589 | 838.0,0.6615 590 | 839.0,0.6612 591 | 840.0,0.6624 592 | 841.0,0.6635 593 | 842.0,0.6618 594 | 843.0,0.6596 595 | 844.0,0.6585 596 | 845.0,0.6589 597 | 846.0,0.6602 598 | 847.0,0.6605 599 | 848.0,0.6577 600 | 849.0,0.6319 601 | 850.0,0.4742 602 | 851.0,0.1817 603 | 852.0,0.0235 604 | 853.0,0.0 605 | 854.0,0.0 606 | 855.0,0.0 607 | 856.0,0.0 608 | 857.0,0.0 609 | 858.0,0.0 610 | 859.0,0.0 611 | 860.0,0.0 612 | 861.0,0.0 613 | 862.0,0.0 614 | 863.0,0.0 615 | 864.0,0.0 616 | 865.0,0.0 617 | 866.0,0.0 618 | 867.0,0.0 619 | 868.0,0.0 620 | 869.0,0.0 621 | 870.0,0.0 622 | 871.0,0.0 623 | 872.0,0.0 624 | 873.0,0.0 625 | 874.0,0.0 626 | 875.0,0.0 627 | 876.0,0.0 628 | 877.0,0.0 629 | 878.0,0.0 630 | 879.0,0.0 631 | 880.0,0.0 632 | 881.0,0.0 633 | 882.0,0.0 634 | 883.0,0.0 635 | 884.0,0.0 636 | 885.0,0.0 637 | 886.0,0.0 638 | 887.0,0.0 639 | 888.0,0.0 640 | 889.0,0.0 641 | 890.0,0.0 642 | 891.0,0.0 643 | 892.0,0.0 644 | 893.0,0.0 645 | 894.0,0.0 646 | 895.0,0.0 647 | 896.0,0.0 648 | 897.0,0.0 649 | 898.0,0.0 650 | 899.0,0.0 651 | 900.0,0.0 652 | -------------------------------------------------------------------------------- /calibration/RP02-1622152-SC.csv: -------------------------------------------------------------------------------- 1 | 250.0,0.0 2 | 251.0,0.0 3 | 252.0,0.0 4 | 253.0,0.0 5 | 254.0,0.0 6 | 255.0,0.0 7 | 256.0,0.0 8 | 257.0,0.0 9 | 258.0,0.0 10 | 259.0,0.0 11 | 260.0,0.0 12 | 261.0,0.0 13 | 262.0,0.0 14 | 263.0,0.0 15 | 264.0,0.0 16 | 265.0,0.0 17 | 266.0,0.0 18 | 267.0,0.0 19 | 268.0,0.0 20 | 269.0,0.0 21 | 270.0,0.0 22 | 271.0,0.0 23 | 272.0,0.0 24 | 273.0,0.0 25 | 274.0,0.0 26 | 275.0,0.0 27 | 276.0,0.0 28 | 277.0,0.0 29 | 278.0,0.0 30 | 279.0,0.0 31 | 280.0,0.0 32 | 281.0,0.0 33 | 282.0,0.0 34 | 283.0,0.0 35 | 284.0,0.0 36 | 285.0,0.0 37 | 286.0,0.0 38 | 287.0,0.0 39 | 288.0,0.0 40 | 289.0,0.0 41 | 290.0,0.0 42 | 291.0,0.0 43 | 292.0,0.0 44 | 293.0,0.0 45 | 294.0,0.0 46 | 295.0,0.0 47 | 296.0,0.0 48 | 297.0,0.0 49 | 298.0,0.0 50 | 299.0,0.0 51 | 300.0,0.0 52 | 301.0,0.0 53 | 302.0,0.0 54 | 303.0,0.0 55 | 304.0,0.0 56 | 305.0,0.0 57 | 306.0,0.0 58 | 307.0,0.0 59 | 308.0,0.0 60 | 309.0,0.0 61 | 310.0,0.0 62 | 311.0,0.0 63 | 312.0,0.0 64 | 313.0,0.0 65 | 314.0,0.0 66 | 315.0,0.0 67 | 316.0,0.0 68 | 317.0,0.0 69 | 318.0,0.0 70 | 319.0,0.0 71 | 320.0,0.0 72 | 321.0,0.0 73 | 322.0,0.0 74 | 323.0,0.0 75 | 324.0,0.0 76 | 325.0,0.0 77 | 326.0,0.0 78 | 327.0,0.0 79 | 328.0,0.0 80 | 329.0,0.0 81 | 330.0,0.0 82 | 331.0,0.0 83 | 332.0,0.0 84 | 333.0,0.0 85 | 334.0,0.0 86 | 335.0,0.0 87 | 336.0,0.0 88 | 337.0,0.0 89 | 338.0,0.0 90 | 339.0,0.0 91 | 340.0,0.0 92 | 341.0,0.0 93 | 342.0,0.0 94 | 343.0,0.0 95 | 344.0,0.0 96 | 345.0,0.0 97 | 346.0,0.0 98 | 347.0,0.0 99 | 348.0,0.0 100 | 349.0,0.0 101 | 350.0,0.0 102 | 351.0,0.0 103 | 352.0,0.0 104 | 353.0,0.0 105 | 354.0,0.0 106 | 355.0,0.0 107 | 356.0,0.0 108 | 357.0,0.0 109 | 358.0,0.0245 110 | 359.0,0.1892 111 | 360.0,0.4924 112 | 361.0,0.6512 113 | 362.0,0.6705 114 | 363.0,0.6707 115 | 364.0,0.6742 116 | 365.0,0.6784 117 | 366.0,0.6796 118 | 367.0,0.6754 119 | 368.0,0.6685 120 | 369.0,0.6647 121 | 370.0,0.6646 122 | 371.0,0.666 123 | 372.0,0.6677 124 | 373.0,0.6658 125 | 374.0,0.6633 126 | 375.0,0.6645 127 | 376.0,0.6675 128 | 377.0,0.6669 129 | 378.0,0.6642 130 | 379.0,0.6627 131 | 380.0,0.6621 132 | 381.0,0.6644 133 | 382.0,0.6657 134 | 383.0,0.6631 135 | 384.0,0.6588 136 | 385.0,0.6585 137 | 386.0,0.6622 138 | 387.0,0.6634 139 | 388.0,0.6611 140 | 389.0,0.6599 141 | 390.0,0.6596 142 | 391.0,0.6595 143 | 392.0,0.6601 144 | 393.0,0.6621 145 | 394.0,0.6632 146 | 395.0,0.6633 147 | 396.0,0.6644 148 | 397.0,0.6658 149 | 398.0,0.6662 150 | 399.0,0.6657 151 | 400.0,0.6654 152 | 401.0,0.6661 153 | 402.0,0.6658 154 | 403.0,0.6645 155 | 404.0,0.6651 156 | 405.0,0.6674 157 | 406.0,0.6699 158 | 407.0,0.6707 159 | 408.0,0.6704 160 | 409.0,0.6704 161 | 410.0,0.6708 162 | 411.0,0.6716 163 | 412.0,0.6726 164 | 413.0,0.6738 165 | 414.0,0.675 166 | 415.0,0.6759 167 | 416.0,0.6767 168 | 417.0,0.6774 169 | 418.0,0.6776 170 | 419.0,0.6776 171 | 420.0,0.6785 172 | 421.0,0.6788 173 | 422.0,0.6784 174 | 423.0,0.6786 175 | 424.0,0.6786 176 | 425.0,0.6785 177 | 426.0,0.6786 178 | 427.0,0.6778 179 | 428.0,0.6761 180 | 429.0,0.6758 181 | 430.0,0.6761 182 | 431.0,0.6764 183 | 432.0,0.677 184 | 433.0,0.6774 185 | 434.0,0.6778 186 | 435.0,0.678 187 | 436.0,0.678 188 | 437.0,0.6777 189 | 438.0,0.6775 190 | 439.0,0.6775 191 | 440.0,0.6771 192 | 441.0,0.6766 193 | 442.0,0.676 194 | 443.0,0.6754 195 | 444.0,0.675 196 | 445.0,0.6751 197 | 446.0,0.6761 198 | 447.0,0.6773 199 | 448.0,0.6775 200 | 449.0,0.6757 201 | 450.0,0.674 202 | 451.0,0.6739 203 | 452.0,0.6744 204 | 453.0,0.6741 205 | 454.0,0.6736 206 | 455.0,0.6739 207 | 456.0,0.6742 208 | 457.0,0.674 209 | 458.0,0.6737 210 | 459.0,0.674 211 | 460.0,0.6745 212 | 461.0,0.6741 213 | 462.0,0.6735 214 | 463.0,0.6734 215 | 464.0,0.6736 216 | 465.0,0.674 217 | 466.0,0.6736 218 | 467.0,0.6729 219 | 468.0,0.673 220 | 469.0,0.6733 221 | 470.0,0.673 222 | 471.0,0.6725 223 | 472.0,0.6726 224 | 473.0,0.6732 225 | 474.0,0.6737 226 | 475.0,0.6737 227 | 476.0,0.6736 228 | 477.0,0.6732 229 | 478.0,0.6727 230 | 479.0,0.6727 231 | 480.0,0.6731 232 | 481.0,0.6737 233 | 482.0,0.6743 234 | 483.0,0.675 235 | 484.0,0.6747 236 | 485.0,0.6741 237 | 486.0,0.6745 238 | 487.0,0.675 239 | 488.0,0.675 240 | 489.0,0.6751 241 | 490.0,0.6753 242 | 491.0,0.6751 243 | 492.0,0.6754 244 | 493.0,0.6765 245 | 494.0,0.6769 246 | 495.0,0.6765 247 | 496.0,0.676 248 | 497.0,0.6759 249 | 498.0,0.6764 250 | 499.0,0.6768 251 | 500.0,0.6769 252 | 501.0,0.6772 253 | 502.0,0.6779 254 | 503.0,0.6781 255 | 504.0,0.6782 256 | 505.0,0.678 257 | 506.0,0.678 258 | 507.0,0.6787 259 | 508.0,0.6791 260 | 509.0,0.6787 261 | 510.0,0.6785 262 | 511.0,0.6788 263 | 512.0,0.6792 264 | 513.0,0.6797 265 | 514.0,0.6802 266 | 515.0,0.6807 267 | 516.0,0.681 268 | 517.0,0.681 269 | 518.0,0.6808 270 | 519.0,0.6808 271 | 520.0,0.6814 272 | 521.0,0.6821 273 | 522.0,0.6824 274 | 523.0,0.6821 275 | 524.0,0.6815 276 | 525.0,0.681 277 | 526.0,0.6808 278 | 527.0,0.6816 279 | 528.0,0.6826 280 | 529.0,0.6832 281 | 530.0,0.6836 282 | 531.0,0.6839 283 | 532.0,0.6838 284 | 533.0,0.6835 285 | 534.0,0.6832 286 | 535.0,0.683 287 | 536.0,0.6834 288 | 537.0,0.6837 289 | 538.0,0.6838 290 | 539.0,0.6838 291 | 540.0,0.6839 292 | 541.0,0.6841 293 | 542.0,0.6843 294 | 543.0,0.6844 295 | 544.0,0.6844 296 | 545.0,0.6845 297 | 546.0,0.6851 298 | 547.0,0.6856 299 | 548.0,0.6857 300 | 549.0,0.6857 301 | 550.0,0.6858 302 | 551.0,0.6856 303 | 552.0,0.6858 304 | 553.0,0.6865 305 | 554.0,0.6867 306 | 555.0,0.6869 307 | 556.0,0.6872 308 | 557.0,0.6871 309 | 558.0,0.6872 310 | 559.0,0.6874 311 | 560.0,0.6876 312 | 561.0,0.6871 313 | 562.0,0.6862 314 | 563.0,0.6857 315 | 564.0,0.6857 316 | 565.0,0.6856 317 | 566.0,0.686 318 | 567.0,0.6868 319 | 568.0,0.6873 320 | 569.0,0.6874 321 | 570.0,0.6876 322 | 571.0,0.6878 323 | 572.0,0.6881 324 | 573.0,0.6886 325 | 574.0,0.6892 326 | 575.0,0.6895 327 | 576.0,0.6892 328 | 577.0,0.6883 329 | 578.0,0.6877 330 | 579.0,0.688 331 | 580.0,0.6884 332 | 581.0,0.6888 333 | 582.0,0.6891 334 | 583.0,0.6888 335 | 584.0,0.6889 336 | 585.0,0.689 337 | 586.0,0.6888 338 | 587.0,0.6888 339 | 588.0,0.6888 340 | 589.0,0.6888 341 | 590.0,0.6887 342 | 591.0,0.6886 343 | 592.0,0.6888 344 | 593.0,0.689 345 | 594.0,0.6887 346 | 595.0,0.6882 347 | 596.0,0.6884 348 | 597.0,0.689 349 | 598.0,0.6892 350 | 599.0,0.6892 351 | 600.0,0.6893 352 | 601.0,0.6893 353 | 602.0,0.6894 354 | 603.0,0.6896 355 | 604.0,0.6898 356 | 605.0,0.6899 357 | 606.0,0.6897 358 | 607.0,0.6894 359 | 608.0,0.6893 360 | 609.0,0.6897 361 | 610.0,0.6904 362 | 611.0,0.6907 363 | 612.0,0.6903 364 | 613.0,0.6897 365 | 614.0,0.6896 366 | 615.0,0.6906 367 | 616.0,0.6913 368 | 617.0,0.6914 369 | 618.0,0.6914 370 | 619.0,0.6914 371 | 620.0,0.6912 372 | 621.0,0.691 373 | 622.0,0.6911 374 | 623.0,0.6914 375 | 624.0,0.6915 376 | 625.0,0.6911 377 | 626.0,0.6909 378 | 627.0,0.6915 379 | 628.0,0.6921 380 | 629.0,0.6926 381 | 630.0,0.6926 382 | 631.0,0.6917 383 | 632.0,0.6911 384 | 633.0,0.6917 385 | 634.0,0.6924 386 | 635.0,0.6919 387 | 636.0,0.691 388 | 637.0,0.691 389 | 638.0,0.6911 390 | 639.0,0.691 391 | 640.0,0.6911 392 | 641.0,0.6914 393 | 642.0,0.6916 394 | 643.0,0.6916 395 | 644.0,0.6916 396 | 645.0,0.6914 397 | 646.0,0.6907 398 | 647.0,0.6904 399 | 648.0,0.6907 400 | 649.0,0.6907 401 | 650.0,0.6906 402 | 651.0,0.6905 403 | 652.0,0.6902 404 | 653.0,0.6902 405 | 654.0,0.6902 406 | 655.0,0.6898 407 | 656.0,0.6893 408 | 657.0,0.689 409 | 658.0,0.6885 410 | 659.0,0.6884 411 | 660.0,0.6895 412 | 661.0,0.6902 413 | 662.0,0.6904 414 | 663.0,0.6904 415 | 664.0,0.6899 416 | 665.0,0.6891 417 | 666.0,0.6887 418 | 667.0,0.6881 419 | 668.0,0.6876 420 | 669.0,0.6872 421 | 670.0,0.6866 422 | 671.0,0.6858 423 | 672.0,0.6853 424 | 673.0,0.6846 425 | 674.0,0.6847 426 | 675.0,0.6855 427 | 676.0,0.6854 428 | 677.0,0.6841 429 | 678.0,0.683 430 | 679.0,0.6829 431 | 680.0,0.6832 432 | 681.0,0.6837 433 | 682.0,0.6837 434 | 683.0,0.6835 435 | 684.0,0.6829 436 | 685.0,0.6818 437 | 686.0,0.6814 438 | 687.0,0.6823 439 | 688.0,0.6829 440 | 689.0,0.6828 441 | 690.0,0.6824 442 | 691.0,0.6816 443 | 692.0,0.6809 444 | 693.0,0.6812 445 | 694.0,0.6827 446 | 695.0,0.6837 447 | 696.0,0.6831 448 | 697.0,0.6819 449 | 698.0,0.6814 450 | 699.0,0.6814 451 | 700.0,0.6807 452 | 701.0,0.6805 453 | 702.0,0.6807 454 | 703.0,0.6807 455 | 704.0,0.6807 456 | 705.0,0.6806 457 | 706.0,0.6808 458 | 707.0,0.6811 459 | 708.0,0.6807 460 | 709.0,0.6802 461 | 710.0,0.6803 462 | 711.0,0.6804 463 | 712.0,0.6794 464 | 713.0,0.6786 465 | 714.0,0.6785 466 | 715.0,0.6785 467 | 716.0,0.6786 468 | 717.0,0.6792 469 | 718.0,0.6793 470 | 719.0,0.6785 471 | 720.0,0.6779 472 | 721.0,0.6783 473 | 722.0,0.6796 474 | 723.0,0.6799 475 | 724.0,0.6784 476 | 725.0,0.6773 477 | 726.0,0.6776 478 | 727.0,0.6783 479 | 728.0,0.6793 480 | 729.0,0.6799 481 | 730.0,0.6789 482 | 731.0,0.6774 483 | 732.0,0.6772 484 | 733.0,0.6772 485 | 734.0,0.6772 486 | 735.0,0.6769 487 | 736.0,0.6767 488 | 737.0,0.6766 489 | 738.0,0.6763 490 | 739.0,0.6758 491 | 740.0,0.6753 492 | 741.0,0.6752 493 | 742.0,0.6749 494 | 743.0,0.6745 495 | 744.0,0.6744 496 | 745.0,0.6747 497 | 746.0,0.6747 498 | 747.0,0.6732 499 | 748.0,0.6722 500 | 749.0,0.6718 501 | 750.0,0.6713 502 | 751.0,0.6711 503 | 752.0,0.6717 504 | 753.0,0.6721 505 | 754.0,0.6715 506 | 755.0,0.6708 507 | 756.0,0.6707 508 | 757.0,0.6707 509 | 758.0,0.6701 510 | 759.0,0.6697 511 | 760.0,0.6693 512 | 761.0,0.6688 513 | 762.0,0.6684 514 | 763.0,0.6681 515 | 764.0,0.6678 516 | 765.0,0.6678 517 | 766.0,0.6675 518 | 767.0,0.6667 519 | 768.0,0.6662 520 | 769.0,0.6665 521 | 770.0,0.6666 522 | 771.0,0.6663 523 | 772.0,0.6651 524 | 773.0,0.6642 525 | 774.0,0.6639 526 | 775.0,0.6636 527 | 776.0,0.6631 528 | 777.0,0.6625 529 | 778.0,0.6623 530 | 779.0,0.6619 531 | 780.0,0.6614 532 | 781.0,0.661 533 | 782.0,0.6605 534 | 783.0,0.6605 535 | 784.0,0.6606 536 | 785.0,0.6599 537 | 786.0,0.6588 538 | 787.0,0.6585 539 | 788.0,0.6585 540 | 789.0,0.6578 541 | 790.0,0.6575 542 | 791.0,0.6574 543 | 792.0,0.6568 544 | 793.0,0.6562 545 | 794.0,0.6553 546 | 795.0,0.6547 547 | 796.0,0.6549 548 | 797.0,0.655 549 | 798.0,0.6551 550 | 799.0,0.6556 551 | 800.0,0.6555 552 | 801.0,0.6546 553 | 802.0,0.6541 554 | 803.0,0.6541 555 | 804.0,0.6536 556 | 805.0,0.653 557 | 806.0,0.6518 558 | 807.0,0.6517 559 | 808.0,0.6522 560 | 809.0,0.6517 561 | 810.0,0.6504 562 | 811.0,0.6499 563 | 812.0,0.6505 564 | 813.0,0.6514 565 | 814.0,0.6515 566 | 815.0,0.6511 567 | 816.0,0.6503 568 | 817.0,0.6496 569 | 818.0,0.6494 570 | 819.0,0.6497 571 | 820.0,0.6505 572 | 821.0,0.6508 573 | 822.0,0.6497 574 | 823.0,0.648 575 | 824.0,0.647 576 | 825.0,0.6468 577 | 826.0,0.6459 578 | 827.0,0.6454 579 | 828.0,0.6467 580 | 829.0,0.6474 581 | 830.0,0.6473 582 | 831.0,0.6481 583 | 832.0,0.6491 584 | 833.0,0.6486 585 | 834.0,0.6472 586 | 835.0,0.6463 587 | 836.0,0.6447 588 | 837.0,0.6431 589 | 838.0,0.6431 590 | 839.0,0.6443 591 | 840.0,0.6459 592 | 841.0,0.6461 593 | 842.0,0.6448 594 | 843.0,0.6443 595 | 844.0,0.6442 596 | 845.0,0.6437 597 | 846.0,0.6446 598 | 847.0,0.6473 599 | 848.0,0.6486 600 | 849.0,0.6251 601 | 850.0,0.4684 602 | 851.0,0.1791 603 | 852.0,0.0231 604 | 853.0,0.0 605 | 854.0,0.0 606 | 855.0,0.0 607 | 856.0,0.0 608 | 857.0,0.0 609 | 858.0,0.0 610 | 859.0,0.0 611 | 860.0,0.0 612 | 861.0,0.0 613 | 862.0,0.0 614 | 863.0,0.0 615 | 864.0,0.0 616 | 865.0,0.0 617 | 866.0,0.0 618 | 867.0,0.0 619 | 868.0,0.0 620 | 869.0,0.0 621 | 870.0,0.0 622 | 871.0,0.0 623 | 872.0,0.0 624 | 873.0,0.0 625 | 874.0,0.0 626 | 875.0,0.0 627 | 876.0,0.0 628 | 877.0,0.0 629 | 878.0,0.0 630 | 879.0,0.0 631 | 880.0,0.0 632 | 881.0,0.0 633 | 882.0,0.0 634 | 883.0,0.0 635 | 884.0,0.0 636 | 885.0,0.0 637 | 886.0,0.0 638 | 887.0,0.0 639 | 888.0,0.0 640 | 889.0,0.0 641 | 890.0,0.0 642 | 891.0,0.0 643 | 892.0,0.0 644 | 893.0,0.0 645 | 894.0,0.0 646 | 895.0,0.0 647 | 896.0,0.0 648 | 897.0,0.0 649 | 898.0,0.0 650 | 899.0,0.0 651 | 900.0,0.0 652 | -------------------------------------------------------------------------------- /calibration/RP04-1806002-SC.csv: -------------------------------------------------------------------------------- 1 | Wavelength (nm),Reflectance 2 | 250,49.901862 3 | 251,49.832032 4 | 252,49.766288 5 | 253,49.717399 6 | 254,49.615813 7 | 255,49.527184 8 | 256,49.470175 9 | 257,49.384848 10 | 258,49.324898 11 | 259,49.290495 12 | 260,49.230678 13 | 261,49.150224 14 | 262,49.114494 15 | 263,49.101499 16 | 264,49.045483 17 | 265,49.003076 18 | 266,48.980207 19 | 267,49.011184 20 | 268,48.940621 21 | 269,48.900661 22 | 270,48.898757 23 | 271,48.925217 24 | 272,48.929311 25 | 273,48.89483 26 | 274,48.820243 27 | 275,48.791543 28 | 276,48.769115 29 | 277,48.815296 30 | 278,48.809855 31 | 279,48.766377 32 | 280,48.753984 33 | 281,48.749944 34 | 282,48.72047 35 | 283,48.743811 36 | 284,48.707487 37 | 285,48.696994 38 | 286,48.766163 39 | 287,48.746015 40 | 288,48.748846 41 | 289,48.775368 42 | 290,48.812023 43 | 291,48.771199 44 | 292,48.799693 45 | 293,48.820744 46 | 294,48.751855 47 | 295,48.827653 48 | 296,48.848083 49 | 297,48.845934 50 | 298,48.83592 51 | 299,48.831873 52 | 300,48.815778 53 | 301,48.827736 54 | 302,48.845061 55 | 303,48.813947 56 | 304,48.840886 57 | 305,48.849711 58 | 306,48.809554 59 | 307,48.896175 60 | 308,48.945884 61 | 309,48.902407 62 | 310,48.842354 63 | 311,48.838909 64 | 312,48.786016 65 | 313,48.85805 66 | 314,48.80662 67 | 315,48.872551 68 | 316,48.832587 69 | 317,48.696978 70 | 318,48.695546 71 | 319,48.639999 72 | 320,48.671532 73 | 321,48.779213 74 | 322,48.582865 75 | 323,48.607938 76 | 324,48.666362 77 | 325,48.541926 78 | 326,48.600089 79 | 327,48.574099 80 | 328,48.453977 81 | 329,48.413214 82 | 330,48.452426 83 | 331,48.385727 84 | 332,48.419893 85 | 333,48.428104 86 | 334,48.417844 87 | 335,48.419071 88 | 336,48.430646 89 | 337,48.332083 90 | 338,48.310058 91 | 339,48.279598 92 | 340,48.28381 93 | 341,48.314246 94 | 342,48.357884 95 | 343,48.336439 96 | 344,48.356472 97 | 345,48.326084 98 | 346,48.286254 99 | 347,48.316462 100 | 348,48.29935 101 | 349,48.331439 102 | 350,48.299259 103 | 351,48.371994 104 | 352,48.28669 105 | 353,48.322813 106 | 354,48.318801 107 | 355,48.298261 108 | 356,48.276364 109 | 357,48.277978 110 | 358,48.258998 111 | 359,48.307042 112 | 360,48.277528 113 | 361,48.218594 114 | 362,48.278612 115 | 363,48.277062 116 | 364,48.307968 117 | 365,48.210622 118 | 366,48.287384 119 | 367,48.264492 120 | 368,48.338323 121 | 369,48.314075 122 | 370,48.26416 123 | 371,48.329519 124 | 372,48.191372 125 | 373,48.271729 126 | 374,48.313783 127 | 375,48.204573 128 | 376,48.299615 129 | 377,48.384155 130 | 378,48.358459 131 | 379,48.417144 132 | 380,48.354234 133 | 381,48.358213 134 | 382,48.384212 135 | 383,48.370969 136 | 384,48.381329 137 | 385,48.38559 138 | 386,48.364826 139 | 387,48.34676 140 | 388,48.339269 141 | 389,48.328116 142 | 390,48.334449 143 | 391,48.312791 144 | 392,48.328882 145 | 393,48.340994 146 | 394,48.315117 147 | 395,48.298929 148 | 396,48.308705 149 | 397,48.309548 150 | 398,48.291071 151 | 399,48.313812 152 | 400,48.346554 153 | 401,48.344698 154 | 402,48.340867 155 | 403,48.343119 156 | 404,48.333864 157 | 405,48.279082 158 | 406,48.314276 159 | 407,48.362913 160 | 408,48.322448 161 | 409,48.330502 162 | 410,48.316138 163 | 411,48.323258 164 | 412,48.038124 165 | 413,48.355781 166 | 414,48.286055 167 | 415,48.272801 168 | 416,48.278334 169 | 417,48.322142 170 | 418,48.31726 171 | 419,48.335462 172 | 420,48.326164 173 | 421,48.325002 174 | 422,48.354496 175 | 423,48.310591 176 | 424,48.343642 177 | 425,48.365068 178 | 426,48.374752 179 | 427,48.334929 180 | 428,48.356817 181 | 429,48.381074 182 | 430,48.407889 183 | 431,48.400647 184 | 432,48.388909 185 | 433,48.395997 186 | 434,48.367681 187 | 435,48.395099 188 | 436,48.400717 189 | 437,48.357291 190 | 438,48.359293 191 | 439,48.375422 192 | 440,48.347006 193 | 441,48.036478 194 | 442,48.369696 195 | 443,48.391377 196 | 444,48.409047 197 | 445,48.401593 198 | 446,48.412322 199 | 447,48.421272 200 | 448,48.430114 201 | 449,48.468935 202 | 450,48.500344 203 | 451,48.480246 204 | 452,48.483367 205 | 453,48.486221 206 | 454,48.499744 207 | 455,48.513576 208 | 456,48.506246 209 | 457,48.524053 210 | 458,48.540735 211 | 459,48.526469 212 | 460,48.509991 213 | 461,48.500729 214 | 462,48.488062 215 | 463,48.514728 216 | 464,48.528348 217 | 465,48.523823 218 | 466,48.53376 219 | 467,48.519198 220 | 468,48.50547 221 | 469,48.504732 222 | 470,48.496032 223 | 471,48.506687 224 | 472,48.51877 225 | 473,48.514463 226 | 474,48.509255 227 | 475,48.489268 228 | 476,48.483041 229 | 477,48.478538 230 | 478,48.485284 231 | 479,48.503033 232 | 480,48.507856 233 | 481,48.527548 234 | 482,48.556359 235 | 483,48.542056 236 | 484,48.552749 237 | 485,48.568022 238 | 486,48.581135 239 | 487,48.605629 240 | 488,48.580285 241 | 489,48.576883 242 | 490,48.577357 243 | 491,48.599234 244 | 492,48.630432 245 | 493,48.650965 246 | 494,48.62625 247 | 495,48.60958 248 | 496,48.629314 249 | 497,48.628052 250 | 498,48.636994 251 | 499,48.669465 252 | 500,48.674485 253 | 501,48.673051 254 | 502,48.695508 255 | 503,48.718945 256 | 504,48.677516 257 | 505,48.646079 258 | 506,48.691358 259 | 507,48.670188 260 | 508,48.639847 261 | 509,48.675135 262 | 510,48.685472 263 | 511,48.679828 264 | 512,48.678474 265 | 513,48.655074 266 | 514,48.636762 267 | 515,48.062903 268 | 516,48.636584 269 | 517,48.640861 270 | 518,48.649285 271 | 519,48.664188 272 | 520,48.630941 273 | 521,48.639484 274 | 522,48.665044 275 | 523,48.654198 276 | 524,48.645514 277 | 525,48.655185 278 | 526,48.663269 279 | 527,48.651478 280 | 528,48.681113 281 | 529,48.673884 282 | 530,48.663913 283 | 531,48.668236 284 | 532,48.682404 285 | 533,48.677809 286 | 534,48.685439 287 | 535,48.682547 288 | 536,48.657788 289 | 537,48.692969 290 | 538,48.680596 291 | 539,48.679762 292 | 540,48.685612 293 | 541,48.693875 294 | 542,48.695462 295 | 543,48.709244 296 | 544,48.722597 297 | 545,48.701996 298 | 546,48.691316 299 | 547,48.71125 300 | 548,48.743624 301 | 549,48.685695 302 | 550,48.710354 303 | 551,48.719757 304 | 552,48.738154 305 | 553,48.727637 306 | 554,48.722658 307 | 555,48.720261 308 | 556,48.751001 309 | 557,48.727751 310 | 558,48.714458 311 | 559,48.707999 312 | 560,48.74051 313 | 561,48.743157 314 | 562,48.078161 315 | 563,48.725152 316 | 564,48.726839 317 | 565,48.775858 318 | 566,48.717721 319 | 567,48.703616 320 | 568,48.741869 321 | 569,48.741555 322 | 570,48.755322 323 | 571,48.756143 324 | 572,48.751802 325 | 573,48.73353 326 | 574,48.801166 327 | 575,48.791089 328 | 576,48.736649 329 | 577,48.77727 330 | 578,48.800739 331 | 579,48.817663 332 | 580,48.795422 333 | 581,48.764341 334 | 582,48.792945 335 | 583,48.767497 336 | 584,48.822201 337 | 585,48.849749 338 | 586,48.785576 339 | 587,48.810754 340 | 588,48.819354 341 | 589,48.802453 342 | 590,48.796951 343 | 591,48.792811 344 | 592,48.775917 345 | 593,48.84778 346 | 594,48.798235 347 | 595,48.803502 348 | 596,48.810659 349 | 597,48.798094 350 | 598,48.802474 351 | 599,48.847474 352 | 600,48.81904 353 | 601,48.81001 354 | 602,48.80034 355 | 603,48.791482 356 | 604,48.764584 357 | 605,48.814675 358 | 606,48.772569 359 | 607,48.805918 360 | 608,48.776226 361 | 609,48.775156 362 | 610,48.846107 363 | 611,48.776432 364 | 612,48.75292 365 | 613,48.800598 366 | 614,48.775082 367 | 615,48.813462 368 | 616,48.810548 369 | 617,48.811661 370 | 618,48.828614 371 | 619,48.826144 372 | 620,48.819734 373 | 621,48.815959 374 | 622,48.819444 375 | 623,48.806167 376 | 624,48.828745 377 | 625,48.831237 378 | 626,48.790401 379 | 627,48.761857 380 | 628,48.774955 381 | 629,48.776295 382 | 630,48.839976 383 | 631,48.822788 384 | 632,48.797378 385 | 633,48.809433 386 | 634,48.777428 387 | 635,48.774429 388 | 636,48.80509 389 | 637,48.801427 390 | 638,48.821084 391 | 639,48.823469 392 | 640,48.829767 393 | 641,48.821518 394 | 642,48.801609 395 | 643,48.801509 396 | 644,48.807979 397 | 645,48.816484 398 | 646,48.80929 399 | 647,48.786394 400 | 648,48.77045 401 | 649,48.807452 402 | 650,48.792956 403 | 651,48.757819 404 | 652,48.793262 405 | 653,48.861351 406 | 654,48.847244 407 | 655,48.860408 408 | 656,48.81712 409 | 657,48.808571 410 | 658,48.826882 411 | 659,48.822413 412 | 660,48.815297 413 | 661,48.815241 414 | 662,48.79037 415 | 663,48.790019 416 | 664,48.816807 417 | 665,48.805496 418 | 666,48.832898 419 | 667,48.852245 420 | 668,48.80264 421 | 669,48.784343 422 | 670,48.825531 423 | 671,48.8244 424 | 672,48.81128 425 | 673,48.805488 426 | 674,48.821233 427 | 675,48.7713 428 | 676,48.73763 429 | 677,48.776496 430 | 678,48.799645 431 | 679,48.822359 432 | 680,48.801343 433 | 681,48.78154 434 | 682,48.795082 435 | 683,48.828061 436 | 684,48.806118 437 | 685,48.824881 438 | 686,48.854758 439 | 687,48.805826 440 | 688,48.786083 441 | 689,48.807197 442 | 690,48.828559 443 | 691,48.818046 444 | 692,48.818169 445 | 693,48.788472 446 | 694,48.805344 447 | 695,48.826149 448 | 696,48.822366 449 | 697,48.814115 450 | 698,48.829891 451 | 699,48.825166 452 | 700,48.819816 453 | 701,48.843799 454 | 702,48.818926 455 | 703,48.783245 456 | 704,48.804459 457 | 705,48.822034 458 | 706,48.782178 459 | 707,48.735864 460 | 708,48.798376 461 | 709,48.814644 462 | 710,48.80214 463 | 711,48.805258 464 | 712,48.788 465 | 713,48.77131 466 | 714,48.742568 467 | 715,48.792859 468 | 716,48.820604 469 | 717,48.803291 470 | 718,48.790489 471 | 719,48.781973 472 | 720,48.786833 473 | 721,48.796694 474 | 722,48.786939 475 | 723,48.793219 476 | 724,48.781781 477 | 725,48.726482 478 | 726,48.77407 479 | 727,48.760307 480 | 728,48.761701 481 | 729,48.778904 482 | 730,48.819632 483 | 731,48.81622 484 | 732,48.804228 485 | 733,48.797972 486 | 734,48.75203 487 | 735,48.7079 488 | 736,48.797498 489 | 737,48.783389 490 | 738,48.767258 491 | 739,48.777227 492 | 740,48.718542 493 | 741,48.816492 494 | 742,48.74358 495 | 743,48.702773 496 | 744,48.715847 497 | 745,48.725965 498 | 746,48.680414 499 | 747,48.7403 500 | 748,48.709426 501 | 749,48.71778 502 | 750,48.756355 503 | 751,48.749149 504 | 752,48.742367 505 | 753,48.716819 506 | 754,48.858484 507 | 755,48.735756 508 | 756,48.735173 509 | 757,48.790095 510 | 758,48.762792 511 | 759,48.758794 512 | 760,48.761744 513 | 761,48.854291 514 | 762,48.788249 515 | 763,48.773682 516 | 764,48.698095 517 | 765,48.702501 518 | 766,48.766943 519 | 767,48.888948 520 | 768,48.841963 521 | 769,48.795017 522 | 770,48.76593 523 | 771,48.759821 524 | 772,48.753461 525 | 773,48.776467 526 | 774,48.742771 527 | 775,48.760796 528 | 776,48.674254 529 | 777,48.761153 530 | 778,48.763679 531 | 779,48.79558 532 | 780,48.68229 533 | 781,48.793194 534 | 782,48.794083 535 | 783,48.717196 536 | 784,48.723229 537 | 785,48.773601 538 | 786,48.777928 539 | 787,48.710835 540 | 788,48.740258 541 | 789,48.702232 542 | 790,48.689636 543 | 791,48.71094 544 | 792,48.883474 545 | 793,48.852829 546 | 794,48.831846 547 | 795,48.682758 548 | 796,48.766773 549 | 797,48.807332 550 | 798,48.715845 551 | 799,48.790745 552 | 800,48.752849 553 | 801,48.760534 554 | 802,48.833629 555 | 803,48.76366 556 | 804,48.914618 557 | 805,48.77223 558 | 806,48.765341 559 | 807,48.733947 560 | 808,48.710275 561 | 809,48.85523 562 | 810,48.694376 563 | 811,48.767581 564 | 812,48.640435 565 | 813,48.759008 566 | 814,48.712641 567 | 815,48.772458 568 | 816,48.700501 569 | 817,48.811982 570 | 818,48.756295 571 | 819,48.781304 572 | 820,48.70058 573 | 821,48.696793 574 | 822,48.697703 575 | 823,48.703654 576 | 824,48.680882 577 | 825,48.687001 578 | 826,48.648107 579 | 827,48.689181 580 | 828,48.68419 581 | 829,48.661869 582 | 830,48.679162 583 | 831,48.729507 584 | 832,48.675258 585 | 833,48.686848 586 | 834,48.691247 587 | 835,48.678836 588 | 836,48.672374 589 | 837,48.67277 590 | 838,48.722834 591 | 839,48.732526 592 | 840,48.702952 593 | 841,48.698386 594 | 842,48.713321 595 | 843,48.706019 596 | 844,48.686337 597 | 845,48.663478 598 | 846,48.667986 599 | 847,48.665026 600 | 848,48.695657 601 | 849,48.692007 602 | 850,48.664873 603 | 851,48.648599 604 | 852,48.655843 605 | 853,48.667715 606 | 854,48.674927 607 | 855,48.680186 608 | 856,48.689655 609 | 857,48.689947 610 | 858,48.652976 611 | 859,48.667459 612 | 860,48.670248 613 | 861,48.675221 614 | 862,48.663039 615 | 863,48.655759 616 | 864,48.6637 617 | 865,48.653296 618 | 866,48.671069 619 | 867,48.66131 620 | 868,48.660164 621 | 869,48.644716 622 | 870,48.646048 623 | 871,48.647604 624 | 872,48.634986 625 | 873,48.640604 626 | 874,48.666046 627 | 875,48.626141 628 | 876,48.640122 629 | 877,48.640477 630 | 878,48.647683 631 | 879,48.644104 632 | 880,48.650011 633 | 881,48.65429 634 | 882,48.647466 635 | 883,48.611739 636 | 884,48.617006 637 | 885,48.645316 638 | 886,48.64322 639 | 887,48.657715 640 | 888,48.609973 641 | 889,48.603667 642 | 890,48.059939 643 | 891,48.613968 644 | 892,48.622905 645 | 893,48.644836 646 | 894,48.577469 647 | 895,48.557767 648 | 896,48.601988 649 | 897,48.605107 650 | 898,48.599799 651 | 899,48.602866 652 | 900,48.061022 653 | -------------------------------------------------------------------------------- /calibration/RP04-1923118-OB.csv: -------------------------------------------------------------------------------- 1 | 250,0.535167685 2 | 251,0.53452483 3 | 252,0.5343019 4 | 253,0.533966455 5 | 254,0.53357559 6 | 255,0.53293146 7 | 256,0.53240557 8 | 257,0.531824095 9 | 258,0.5312529 10 | 259,0.530892775 11 | 260,0.5304574 12 | 261,0.52994818 13 | 262,0.52921809 14 | 263,0.52891028 15 | 264,0.528235495 16 | 265,0.527593155 17 | 266,0.526951025 18 | 267,0.52664793 19 | 268,0.526247075 20 | 269,0.526109155 21 | 270,0.526252575 22 | 271,0.526136245 23 | 272,0.52576835 24 | 273,0.525325205 25 | 274,0.52519193 26 | 275,0.52502539 27 | 276,0.52477239 28 | 277,0.524707975 29 | 278,0.52443889 30 | 279,0.5241249 31 | 280,0.524037115 32 | 281,0.523789715 33 | 282,0.523990845 34 | 283,0.523809885 35 | 284,0.523612985 36 | 285,0.52290262 37 | 286,0.52294067 38 | 287,0.52340302 39 | 288,0.52314584 40 | 289,0.522978625 41 | 290,0.522663875 42 | 291,0.523025005 43 | 292,0.522648435 44 | 293,0.52265417 45 | 294,0.52249575 46 | 295,0.522914275 47 | 296,0.522986385 48 | 297,0.522860175 49 | 298,0.52265903 50 | 299,0.522768625 51 | 300,0.522524385 52 | 301,0.52244084 53 | 302,0.522784505 54 | 303,0.522845295 55 | 304,0.522013785 56 | 305,0.52206547 57 | 306,0.52224455 58 | 307,0.522380735 59 | 308,0.522323775 60 | 309,0.52244347 61 | 310,0.52217347 62 | 311,0.521908725 63 | 312,0.522027475 64 | 313,0.52130765 65 | 314,0.52117231 66 | 315,0.52086072 67 | 316,0.52079087 68 | 317,0.52012746 69 | 318,0.520477745 70 | 319,0.520318555 71 | 320,0.51993609 72 | 321,0.52048082 73 | 322,0.518805175 74 | 323,0.51931888 75 | 324,0.51833801 76 | 325,0.517796495 77 | 326,0.51693495 78 | 327,0.51795147 79 | 328,0.516737685 80 | 329,0.51691914 81 | 330,0.5170833 82 | 331,0.5166048 83 | 332,0.51680222 84 | 333,0.51667296 85 | 334,0.51616523 86 | 335,0.516521655 87 | 336,0.51680724 88 | 337,0.51664931 89 | 338,0.51598996 90 | 339,0.51616529 91 | 340,0.51633528 92 | 341,0.51649069 93 | 342,0.517102155 94 | 343,0.515998255 95 | 344,0.515391235 96 | 345,0.515935635 97 | 346,0.51671749 98 | 347,0.515830165 99 | 348,0.51573144 100 | 349,0.51623872 101 | 350,0.515561375 102 | 351,0.51580132 103 | 352,0.51533121 104 | 353,0.515336645 105 | 354,0.515383835 106 | 355,0.51559338 107 | 356,0.51608675 108 | 357,0.51650068 109 | 358,0.5159401 110 | 359,0.516104175 111 | 360,0.51579234 112 | 361,0.515900055 113 | 362,0.51611426 114 | 363,0.515441495 115 | 364,0.51594912 116 | 365,0.51556528 117 | 366,0.51529954 118 | 367,0.515446705 119 | 368,0.51584185 120 | 369,0.515364035 121 | 370,0.515421295 122 | 371,0.515209465 123 | 372,0.515389855 124 | 373,0.514820135 125 | 374,0.51500396 126 | 375,0.51496904 127 | 376,0.5141078 128 | 377,0.514734445 129 | 378,0.51541812 130 | 379,0.515307925 131 | 380,0.51524868 132 | 381,0.515385145 133 | 382,0.51531708 134 | 383,0.515608515 135 | 384,0.51534993 136 | 385,0.51530677 137 | 386,0.51557698 138 | 387,0.51537896 139 | 388,0.51528367 140 | 389,0.5150503 141 | 390,0.51518486 142 | 391,0.515335805 143 | 392,0.515502035 144 | 393,0.5151493 145 | 394,0.51504164 146 | 395,0.515231355 147 | 396,0.51556815 148 | 397,0.5151158 149 | 398,0.51468831 150 | 399,0.51526068 151 | 400,0.515255705 152 | 401,0.51519312 153 | 402,0.51518973 154 | 403,0.515179415 155 | 404,0.51505956 156 | 405,0.51492619 157 | 406,0.51518255 158 | 407,0.515149605 159 | 408,0.51515633 160 | 409,0.51472888 161 | 410,0.514916135 162 | 411,0.514726915 163 | 412,0.514638215 164 | 413,0.5148837 165 | 414,0.51507724 166 | 415,0.515168355 167 | 416,0.51519544 168 | 417,0.515139295 169 | 418,0.51499267 170 | 419,0.51498911 171 | 420,0.514842615 172 | 421,0.515240165 173 | 422,0.515232985 174 | 423,0.515165565 175 | 424,0.51498553 176 | 425,0.51496034 177 | 426,0.514910445 178 | 427,0.51493306 179 | 428,0.514922575 180 | 429,0.514956005 181 | 430,0.51513383 182 | 431,0.515400215 183 | 432,0.515355505 184 | 433,0.51477437 185 | 434,0.51477072 186 | 435,0.514626195 187 | 436,0.514531505 188 | 437,0.514751885 189 | 438,0.514820775 190 | 439,0.514684955 191 | 440,0.514728565 192 | 441,0.514960825 193 | 442,0.515166735 194 | 443,0.515109505 195 | 444,0.51518153 196 | 445,0.51539494 197 | 446,0.51515323 198 | 447,0.515143335 199 | 448,0.51532102 200 | 449,0.51549558 201 | 450,0.515597595 202 | 451,0.515664895 203 | 452,0.51566427 204 | 453,0.515501455 205 | 454,0.515578685 206 | 455,0.515637085 207 | 456,0.51562906 208 | 457,0.51588465 209 | 458,0.5158362 210 | 459,0.515618895 211 | 460,0.515668385 212 | 461,0.515876635 213 | 462,0.515925925 214 | 463,0.51550964 215 | 464,0.515180855 216 | 465,0.515337685 217 | 466,0.5155426 218 | 467,0.515682285 219 | 468,0.51570792 220 | 469,0.515634855 221 | 470,0.51542337 222 | 471,0.51522832 223 | 472,0.515526255 224 | 473,0.51574646 225 | 474,0.51562567 226 | 475,0.51566216 227 | 476,0.515820835 228 | 477,0.516016895 229 | 478,0.51583094 230 | 479,0.515704795 231 | 480,0.5158298 232 | 481,0.515925745 233 | 482,0.51569865 234 | 483,0.515687625 235 | 484,0.5158341 236 | 485,0.515949405 237 | 486,0.51626935 238 | 487,0.51619145 239 | 488,0.515892035 240 | 489,0.51611791 241 | 490,0.516394375 242 | 491,0.516573385 243 | 492,0.51655298 244 | 493,0.51636203 245 | 494,0.516479825 246 | 495,0.51677009 247 | 496,0.516829975 248 | 497,0.5168363 249 | 498,0.516737395 250 | 499,0.51652405 251 | 500,0.516627615 252 | 501,0.516732135 253 | 502,0.516632905 254 | 503,0.51655746 255 | 504,0.51660972 256 | 505,0.516539005 257 | 506,0.51669438 258 | 507,0.51661585 259 | 508,0.51650514 260 | 509,0.51661908 261 | 510,0.516626275 262 | 511,0.516637895 263 | 512,0.51649327 264 | 513,0.51652303 265 | 514,0.516688795 266 | 515,0.51654557 267 | 516,0.51654916 268 | 517,0.51652429 269 | 518,0.51643898 270 | 519,0.516632705 271 | 520,0.51665743 272 | 521,0.51686091 273 | 522,0.516623475 274 | 523,0.51649351 275 | 524,0.516538655 276 | 525,0.516494835 277 | 526,0.51648086 278 | 527,0.51658713 279 | 528,0.516410005 280 | 529,0.516507885 281 | 530,0.516623465 282 | 531,0.516594585 283 | 532,0.516628255 284 | 533,0.516450085 285 | 534,0.516650415 286 | 535,0.51682738 287 | 536,0.516838005 288 | 537,0.516640715 289 | 538,0.51660307 290 | 539,0.51657299 291 | 540,0.516736065 292 | 541,0.51689559 293 | 542,0.51687192 294 | 543,0.516715495 295 | 544,0.516707405 296 | 545,0.51685442 297 | 546,0.516972265 298 | 547,0.51683514 299 | 548,0.51710126 300 | 549,0.51692002 301 | 550,0.517040045 302 | 551,0.517089065 303 | 552,0.51706753 304 | 553,0.51685855 305 | 554,0.517096675 306 | 555,0.516848125 307 | 556,0.51680511 308 | 557,0.516765555 309 | 558,0.51688874 310 | 559,0.517101705 311 | 560,0.5170944 312 | 561,0.517191515 313 | 562,0.517100555 314 | 563,0.517256035 315 | 564,0.517130835 316 | 565,0.517135485 317 | 566,0.51702515 318 | 567,0.51699859 319 | 568,0.51735174 320 | 569,0.51731715 321 | 570,0.516971 322 | 571,0.517198915 323 | 572,0.517187185 324 | 573,0.5172815 325 | 574,0.5169817 326 | 575,0.517373675 327 | 576,0.517193355 328 | 577,0.516933185 329 | 578,0.516812225 330 | 579,0.51730114 331 | 580,0.517008605 332 | 581,0.51720408 333 | 582,0.51696832 334 | 583,0.5170618 335 | 584,0.51720347 336 | 585,0.517565125 337 | 586,0.517041785 338 | 587,0.516833425 339 | 588,0.51705541 340 | 589,0.517182325 341 | 590,0.517376245 342 | 591,0.517054055 343 | 592,0.51703146 344 | 593,0.51725763 345 | 594,0.517326125 346 | 595,0.517333955 347 | 596,0.517053075 348 | 597,0.516882745 349 | 598,0.517490115 350 | 599,0.517223845 351 | 600,0.51719085 352 | 601,0.516839625 353 | 602,0.51708647 354 | 603,0.51701273 355 | 604,0.516828655 356 | 605,0.517045345 357 | 606,0.516805515 358 | 607,0.5170346 359 | 608,0.51734829 360 | 609,0.51702513 361 | 610,0.517326565 362 | 611,0.516906625 363 | 612,0.51692526 364 | 613,0.517096155 365 | 614,0.517250085 366 | 615,0.51704632 367 | 616,0.517087375 368 | 617,0.51713945 369 | 618,0.516785215 370 | 619,0.517133915 371 | 620,0.517094765 372 | 621,0.516692025 373 | 622,0.516953905 374 | 623,0.51696422 375 | 624,0.516885165 376 | 625,0.516883745 377 | 626,0.516654495 378 | 627,0.51680049 379 | 628,0.516806555 380 | 629,0.51719801 381 | 630,0.51678739 382 | 631,0.516669725 383 | 632,0.517016105 384 | 633,0.517060635 385 | 634,0.51690666 386 | 635,0.51692941 387 | 636,0.51710886 388 | 637,0.51707126 389 | 638,0.51682491 390 | 639,0.516880565 391 | 640,0.51697917 392 | 641,0.516985775 393 | 642,0.517085045 394 | 643,0.51721062 395 | 644,0.517237905 396 | 645,0.517131005 397 | 646,0.517005025 398 | 647,0.516658855 399 | 648,0.51691748 400 | 649,0.51694108 401 | 650,0.516290815 402 | 651,0.51676958 403 | 652,0.516753005 404 | 653,0.51671301 405 | 654,0.516552475 406 | 655,0.516677785 407 | 656,0.51671955 408 | 657,0.51664365 409 | 658,0.51659122 410 | 659,0.516698395 411 | 660,0.51660394 412 | 661,0.5165246 413 | 662,0.516946795 414 | 663,0.516603515 415 | 664,0.51659502 416 | 665,0.51681242 417 | 666,0.51661319 418 | 667,0.516688905 419 | 668,0.516785085 420 | 669,0.516802935 421 | 670,0.51717179 422 | 671,0.51699025 423 | 672,0.516705975 424 | 673,0.516591385 425 | 674,0.516569835 426 | 675,0.516731425 427 | 676,0.516797475 428 | 677,0.516696305 429 | 678,0.516819565 430 | 679,0.516659655 431 | 680,0.51656748 432 | 681,0.516769305 433 | 682,0.516673315 434 | 683,0.516702665 435 | 684,0.51678787 436 | 685,0.51661195 437 | 686,0.516344085 438 | 687,0.516370145 439 | 688,0.516560665 440 | 689,0.516678745 441 | 690,0.51678935 442 | 691,0.51685688 443 | 692,0.51664022 444 | 693,0.516508025 445 | 694,0.516552705 446 | 695,0.516320365 447 | 696,0.51640646 448 | 697,0.51650916 449 | 698,0.51598395 450 | 699,0.51610842 451 | 700,0.51642874 452 | 701,0.51628442 453 | 702,0.5162397 454 | 703,0.516045075 455 | 704,0.51592893 456 | 705,0.51608671 457 | 706,0.516294915 458 | 707,0.516153125 459 | 708,0.51596912 460 | 709,0.515955575 461 | 710,0.51593095 462 | 711,0.51622272 463 | 712,0.51614402 464 | 713,0.515721835 465 | 714,0.515663105 466 | 715,0.515569765 467 | 716,0.515552245 468 | 717,0.51551152 469 | 718,0.51582759 470 | 719,0.516155335 471 | 720,0.516001245 472 | 721,0.515775715 473 | 722,0.51567695 474 | 723,0.51563619 475 | 724,0.51575649 476 | 725,0.516190975 477 | 726,0.5161494 478 | 727,0.51618338 479 | 728,0.51618448 480 | 729,0.516311325 481 | 730,0.516220445 482 | 731,0.51547789 483 | 732,0.51565221 484 | 733,0.51572362 485 | 734,0.51561541 486 | 735,0.51557802 487 | 736,0.515675195 488 | 737,0.515602025 489 | 738,0.51580009 490 | 739,0.515344645 491 | 740,0.51569711 492 | 741,0.5165672 493 | 742,0.51636421 494 | 743,0.515540295 495 | 744,0.51572579 496 | 745,0.515794305 497 | 746,0.51602672 498 | 747,0.515249755 499 | 748,0.51528167 500 | 749,0.5157138 501 | 750,0.51606596 502 | 751,0.51582924 503 | 752,0.51521899 504 | 753,0.514998495 505 | 754,0.51525229 506 | 755,0.515232915 507 | 756,0.514996315 508 | 757,0.515243815 509 | 758,0.515491505 510 | 759,0.516081925 511 | 760,0.515744005 512 | 761,0.515208795 513 | 762,0.514970855 514 | 763,0.514535445 515 | 764,0.51575857 516 | 765,0.515832185 517 | 766,0.51612387 518 | 767,0.51575873 519 | 768,0.51599667 520 | 769,0.51543141 521 | 770,0.5149252 522 | 771,0.51511567 523 | 772,0.51461833 524 | 773,0.51489704 525 | 774,0.51491034 526 | 775,0.514278025 527 | 776,0.515376675 528 | 777,0.51507031 529 | 778,0.51491368 530 | 779,0.514387345 531 | 780,0.5154995 532 | 781,0.5153718 533 | 782,0.515415255 534 | 783,0.51469621 535 | 784,0.51502734 536 | 785,0.51451523 537 | 786,0.514744405 538 | 787,0.514575915 539 | 788,0.51476855 540 | 789,0.515101155 541 | 790,0.515200055 542 | 791,0.514184 543 | 792,0.514403805 544 | 793,0.51461034 545 | 794,0.51385538 546 | 795,0.51510953 547 | 796,0.51485992 548 | 797,0.51433598 549 | 798,0.51508546 550 | 799,0.51445356 551 | 800,0.514922715 552 | 801,0.51545938 553 | 802,0.514896385 554 | 803,0.513880565 555 | 804,0.514905255 556 | 805,0.51427441 557 | 806,0.51416581 558 | 807,0.514222745 559 | 808,0.514894315 560 | 809,0.51476702 561 | 810,0.514730215 562 | 811,0.51437342 563 | 812,0.51443731 564 | 813,0.515037375 565 | 814,0.514065315 566 | 815,0.51435599 567 | 816,0.51398426 568 | 817,0.51390227 569 | 818,0.51488974 570 | 819,0.514667735 571 | 820,0.51426614 572 | 821,0.514256665 573 | 822,0.514115835 574 | 823,0.514276795 575 | 824,0.513997275 576 | 825,0.51415871 577 | 826,0.514197025 578 | 827,0.51401756 579 | 828,0.51420522 580 | 829,0.513877365 581 | 830,0.5139021 582 | 831,0.513972555 583 | 832,0.51381361 584 | 833,0.5141964 585 | 834,0.51430322 586 | 835,0.51429574 587 | 836,0.51428642 588 | 837,0.513979935 589 | 838,0.51413932 590 | 839,0.514073035 591 | 840,0.514264405 592 | 841,0.5141232 593 | 842,0.513880715 594 | 843,0.51405224 595 | 844,0.5138746 596 | 845,0.513950685 597 | 846,0.51368354 598 | 847,0.51374989 599 | 848,0.51360146 600 | 849,0.513672145 601 | 850,0.51354418 602 | 851,0.51363923 603 | 852,0.51358569 604 | 853,0.51363109 605 | 854,0.513453395 606 | 855,0.51342489 607 | 856,0.513481745 608 | 857,0.513443375 609 | 858,0.51358626 610 | 859,0.513173695 611 | 860,0.51310122 612 | 861,0.51324978 613 | 862,0.51329565 614 | 863,0.51334036 615 | 864,0.51314697 616 | 865,0.51299097 617 | 866,0.513131465 618 | 867,0.512968475 619 | 868,0.51332801 620 | 869,0.513111025 621 | 870,0.513152715 622 | 871,0.513044475 623 | 872,0.513121225 624 | 873,0.512907205 625 | 874,0.51297056 626 | 875,0.51296887 627 | 876,0.51292413 628 | 877,0.51266848 629 | 878,0.512845375 630 | 879,0.512755365 631 | 880,0.512787 632 | 881,0.51284581 633 | 882,0.512778435 634 | 883,0.512566835 635 | 884,0.51238274 636 | 885,0.512806055 637 | 886,0.51272044 638 | 887,0.51259587 639 | 888,0.51244687 640 | 889,0.512702695 641 | 890,0.512839015 642 | 891,0.512778295 643 | 892,0.51244354 644 | 893,0.51246906 645 | 894,0.51228598 646 | 895,0.512518255 647 | 896,0.512699755 648 | 897,0.51237151 649 | 898,0.51238678 650 | 899,0.51228898 651 | 900,0.51234108 652 | 901,0.512002805 653 | 902,0.512155365 654 | 903,0.51222658 655 | 904,0.51208221 656 | 905,0.51196674 657 | 906,0.512023115 658 | 907,0.51156843 659 | 908,0.511586895 660 | 909,0.5120468 661 | 910,0.51177663 662 | 911,0.51154949 663 | 912,0.51167964 664 | 913,0.511930695 665 | 914,0.511745285 666 | 915,0.511608495 667 | 916,0.51167313 668 | 917,0.511618125 669 | 918,0.511660845 670 | 919,0.51135416 671 | 920,0.511443295 672 | 921,0.51148291 673 | 922,0.511388265 674 | 923,0.511457685 675 | 924,0.51141813 676 | 925,0.511197125 677 | 926,0.511190745 678 | 927,0.51105918 679 | 928,0.511127855 680 | 929,0.511108005 681 | 930,0.51087727 682 | 931,0.51086988 683 | 932,0.511145235 684 | 933,0.511147695 685 | 934,0.511062875 686 | 935,0.511025385 687 | 936,0.51087904 688 | 937,0.510618705 689 | 938,0.510694835 690 | 939,0.510813965 691 | 940,0.51078748 692 | 941,0.510685715 693 | 942,0.51048159 694 | 943,0.51072852 695 | 944,0.51084656 696 | 945,0.51071908 697 | 946,0.510912995 698 | 947,0.51082743 699 | 948,0.51078216 700 | 949,0.51073716 701 | 950,0.511014645 702 | -------------------------------------------------------------------------------- /config/config-base.yml: -------------------------------------------------------------------------------- 1 | # This is an example yaml configuration for a metashape run 2 | 3 | #### Project-level parameters: 4 | 5 | # Project to load. If not a blank string, this will open an existing project at the path specified. If a blank string, creates a new empty project. 6 | # Even if opening an existing project, all processing on it is saved as a new project (path and name specified below). The original project file is not modified. 7 | load_project: "" 8 | 9 | # The path to the directory of flight photos 10 | # If there are multiple photo folders, set path to the folder that contains all the photo folders, 11 | # or provide a list of paths via the YAML list syntax (e.g., ["/path/to/photo/folder1", "/path/to/photo/folder2"]) 12 | # If there are no photos to add (e.g., this is an existing project that already has photos in it, set to an empty string ("") 13 | photo_path: "" 14 | 15 | # The path to a secondary directory of flight photos, which are only aligned to the project *after* 16 | # all the other processing is done. This is useful if you want to use secondary photos for multiview 17 | # analyses (e.g., species ID) without affecting the photogrammetry. Note that the secondary photos 18 | # are processed in the same way as the primary (e.g., same resolution, same procedure regarding 19 | # separate calibration per path) and that align_photos:reset_alignment must be False and 20 | # align_photos:keep_keypoints must be True. If there are no secondary photos to add, set to an empty 21 | # string (""). 22 | photo_path_secondary: "" 23 | 24 | # Path for exports (e.g., points, DSM, orthomosaic) and processing log. Will be created if does not exist. 25 | output_path: "" 26 | 27 | # Path to save Metashape project file (.psx). Will be created if does not exist 28 | project_path: "" 29 | 30 | # The identifier for the run. Will be used in naming output files. Recommended to include a photoset name and processing parameter set name. 31 | # Optionally, set it to an empty string ("") to use the config file name (minus extension) as the run name 32 | run_name: "" 33 | 34 | # CRS EPSG code that project outputs should be in (projection should be in meter units and intended for the project area) 35 | project_crs: "EPSG::26910" # 26910 is UTM 10N 36 | 37 | # Enable metashape "fine-level task subdivision" which reduces memory use by breaking processing into independent chunks that are run in series. 38 | # Assuming there's enough memory, it seems to run 10-20% faster by disabling subdividing. But large projects can run out memory and fail if subdivide is not enabled. 39 | subdivide_task: True 40 | 41 | # Should CUDA GPU driver be used? Alternative is OpenCL. Metashape uses CUDA by default but we have observed it can cause crashes on HPC infrastructure. 42 | use_cuda: True 43 | 44 | # What value to use for the Metashape tweak "depth_max_gpu_multiplier"? May help to mitigate GPU errors per: https://www.agisoft.com/forum/index.php?topic=11771.0, but doesn't appear to do anything per our testing. Metashape default is 2. 45 | gpu_multiplier: 2 46 | 47 | #### Processing parameters: 48 | ## Steps can be run or skipped using the 'enabled' parameter. If enabled == False, everything else in the step is irrelevant. 49 | ## The metashape functions powering each of these steps are listed in the comments in parentheses. 50 | ## Refer to Metashape documentation for full parameter definitions: https://www.agisoft.com/pdf/metashape_python_api_1_5_0.pdf 51 | ## Parameter names here generally follow the parameter names of the Metashape functions. 52 | 53 | # Should the photos at the path(s) listed above be added to the project? Can disable if, for 54 | # example, you only want to add GCPs (or do additional processing) to an existing project. 55 | addPhotos: # (Metashape: addPhotos) 56 | enabled: True # This applies to the main photos specified in photo_path above. Secondary photos are always added and aligned if a path (or paths) is provided. 57 | separate_calibration_per_path: False # If True, each photo path (i.e. each element in the list supplied to 'photo_path' above) will be calibrated independently. Regardless whether True or False, separate camera *models* are calibrated separately; if True, identical camera *models* are calibrated separately if they are provided as separate paths. This addresses the case where two different instances of the same camera model are used in the same project. Note that when True, the logic for assigning separate calibration to each path assumes that the same camera is used for all photos in the path. 58 | multispectral: False # Is this a multispectral photo set? If RGB, set to False. 59 | use_rtk: False # Whether to use image EXIF RTK flags to make image geospatial accuracy more precise. If enabled but photos don't have RTK data, will treat them as regular photos and use the nofix accuracy. 60 | fix_accuracy: 3 # Accuracy to set for photos that have a RTK fix, in units of the CRS 61 | nofix_accuracy: 25 # Accuracy to set for photos that have no fix, in units of the CRS 62 | # Choose what type of camera you are using: 63 | # Frame: default, uses radial distortion to handle lens distortion 64 | # Spherical: expects image data to come in in the equirectangular format 65 | sensor_type: Metashape.Sensor.Type.Frame # Sets the camera type. Tested choices: Metashape.Sensor.Type.Frame, Metashape.Sensor.Type.Spherical 66 | 67 | calibrateReflectance: # (Metahsape: calibrateReflectance) 68 | enabled: False 69 | panel_filename: "RP04-1923118-OB.csv" # The calibration file must be in the "calibration" folder in the top-level project photos directory. See example panel calibration file in the calibration directory of project repo. 70 | use_reflectance_panels: True 71 | use_sun_sensor: True 72 | 73 | alignPhotos: # (Metashape: matchPhotos, alignCameras) 74 | enabled: True 75 | downscale: 2 # How much to coarsen the photos when searching for tie points. Higher number for blurrier photos or when there are small surfaces that may move between photos (such as leaves). Accepts numbers 2^x (and zero) (https://www.agisoft.com/forum/index.php?topic=11697.0). 76 | adaptive_fitting: True # Should the camera lens model be fit at the same time as aligning photos? 77 | keep_keypoints: True # Should keypoints from matching photos be stored in the project? Required if you later want to add more photos and align them to the previously aligned photos without redoing the original alignment. 78 | reset_alignment: False # When running an alignment, if any of the photos were already aligned, should we keep that alignment? Or reset it so we align everything anew? 79 | generic_preselection: True # When matching photos, use a much-coarsened version of each photo to narrow down the potential neighbors to pair? Works well if the photos have high altitude above the surface and high overlap (e.g. a 120m nadir 90/90 overlap mission), but doesn't work well for low-altitude and/or highly oblique photos (e.g. a 80m 25deg pitch 80/80 overlap mission) 80 | reference_preselection: True # When matching photos, use the camera location data to narrow down the potential neighbors to pair? 81 | reference_preselection_mode: Metashape.ReferencePreselectionSource # When matching photos, use the camera location data to narrow down the potential neighbors to pair? 82 | 83 | # To use GCPs, a 'gcps' folder must exist in the root of the photo folder provided in photo_path 84 | # above (or the first folder, if a list is passed). The contents of the 'gcps' folder are created by 85 | # the prep_gcps.R script. See readme: https://github.com/ucdavis/metashape 86 | addGCPs: 87 | enabled: False 88 | gcp_crs: "EPSG::26910" # CRS EPSG code of GCP coordinates. 26910 (UTM 10 N) is the CRS of the sample RGB photoset. 89 | marker_location_accuracy: 0.1 # Accuracy of GCPs real-world coordinates, in meters. 90 | marker_projection_accuracy: 8 # Accuracy of the identified locations of the GCPs within the images, in pixels. 91 | optimize_w_gcps_only: True # Optimize alignment using GCPs only: required for GCP locations to take precedence over photo GPS data. Disabling it makes GCPs essentially irrelevant. 92 | 93 | filterPointsUSGS: 94 | enabled: False 95 | rec_thresh_percent: 20 96 | rec_thresh_absolute: 15 97 | proj_thresh_percent: 30 98 | proj_thresh_absolute: 2 99 | reproj_thresh_percent: 5 100 | reproj_thresh_absolute: 0.3 101 | 102 | optimizeCameras: # (Metashape: optimizeCameras) 103 | enabled: True 104 | adaptive_fitting: True # Should the camera lens model be fit at the same time as optimizing photos? 105 | export: True # Export the camera locations, now updated from the initial alignment 106 | 107 | # Should an xml file specifying estimated camera locations (transform matrices) be exported? If 108 | # enabled, it is exported once after all alignment-related steps (e.g., align, fliter points, 109 | # optimize cameras) -- even if these steps are disabled -- and then again after aligning the 110 | # secondary set of locations (if performed), overwriting the first file 111 | exportCameras: # (Metashape: exportCameras) 112 | enabled: True 113 | 114 | buildDepthMaps: # (Metashape: buildDepthMaps) 115 | enabled: True 116 | downscale: 4 # How much to coarsen the photos when searching for matches to build the point cloud. For large photosets, values < 4 likely take prohibitively long. Accepts numbers 2^x (https://www.agisoft.com/forum/index.php?topic=11697.0). 117 | filter_mode: Metashape.ModerateFiltering # How to filter the depth map. Options are NoFiltering, MildFiltering, ModerateFiltering, AggressiveFiltering. Aggressive filtering removes detail and makes worse DEMs (at least for forest). NoFiltering takes very long. In trials, it never completed. 118 | reuse_depth: False # Purpose unknown. 119 | max_neighbors: 60 # Maximum number of neighboring photos to use for estimating depth map. Higher numbers may increase accuracy but dramatically increase processing time. 120 | 121 | buildPointCloud: # (Metashape: buildPointCloud, (optionally) classifyGroundPoints, and exportPoints) 122 | enabled: True 123 | keep_depth: True # If False, removes depth maps from project data after building point cloud 124 | max_neighbors: 60 # Maximum number of neighboring photos to use for estimating point cloud. Higher numbers may increase accuracy but dramatically increase processing time. 125 | classify_ground_points: True # Should ground points be classified as a part of this step? Must be enabled (either here or in buildDem, below) if a digital terrain model (DTM) is needed either for orthomosaic or DTM export. Enabling here is an alternative to enabling as a component of buildDem (below). It depends on which stage you want the classification to be done at. If you already have a point cloud but it's unclassified, then don't do it as part of this stage as it would require computing the point cloud again. 126 | export: False # Whether to export point cloud file. 127 | export_format: Metashape.PointCloudFormatCOPC # Export format. Options: Metashape.PointCloudFormatCOPC, Metashape.PointCloudFormatLAZ, Metashape.PointCloudFormatLAS, or other options indicated in the Metashape Python module documentation. We have observed that COPC export takes about 6x longer than LAZ or LAS export on a 32-core machine, but this is still much faster than using Untwine when the files are not stored on a local volume. PDAL is proably faster but requires a lot of memory. COPC is cloud-optimized point cloud (a subset of LAZ format) and is recommended for cloud native visualization and analysis. 128 | classes: "ALL" # Point classes to export. Must be a list. Or can set to "ALL" to use all points. An example of a specific class is: Metashape.PointClass.Ground 129 | remove_after_export: False # Remove point cloud from project after export of all dependencies (DEMs) to reduce the metashape project file size 130 | 131 | classifyGroundPoints: # (Metashape: classifyGroundPoints) # classify points, IF SPECIFIED as a component of buildPointCloud (above) or buildDem (below). Must be enabled (in either location) if a digital terrain model (DTM) is needed either for orthomosaic or DTM export. Definitions here: https://www.agisoft.com/forum/index.php?topic=9328.0 132 | max_angle: 15.0 133 | max_distance: 1.0 134 | cell_size: 50.0 135 | 136 | buildMesh: 137 | enabled: True 138 | face_count: "Metashape.MediumFaceCount" # How many faces to use, Metashape.LowFaceCount, MediumFaceCount, HighFaceCount, CustomFaceCount 139 | face_count_custom: 100000 # Only used if custom number of faces set (above). 140 | export: True # Export the georeferenced mesh. 141 | export_extension: "ply" # Can be any supported 3D mesh extension 142 | # If True, add in a shift to the CRS so the mesh origin is at the camera EXIF average location and points are reported in a local frame - shifted from the normal CRS origin. 143 | # This is necessary when fine mesh detail is needed, since the natural export format of Metashape is float32 for meshes. In most CRS, that results in point quantization in the 5cm-20cm range 144 | # In order to save finer detail, set shift_crs_to_cameras to True, and then apply the inverse shift to the mesh model to recreate the true CRS values 145 | # The shift used is reported using the save_metadata_xml mechanism of exportModel 146 | shift_crs_to_cameras: False 147 | 148 | buildDem: # (Metashape: buildDem, (optionally) classifyGroundPoints, exportRaster) 149 | enabled: True 150 | classify_ground_points: False # Should ground points be classified as part of this step? Note that an alternative is to calculate them as a part of buildPointCloud (above) 151 | surface: ["DTM-ptcloud", "DSM-ptcloud", "DSM-mesh"] # Options: "DTM-ptcloud", "DSM-ptcloud", and/or "DSM-mesh". Type of DEM to export and data to build it from (digital terrain model or digital surface model, and from point cloud or mesh) 152 | resolution: 0 # DSM resolution. Only affects DSM built using the mesh (other DSM types are set by Metashape and not customizable). Note that this also sets the resolution of the orthomosaic built from this DSM, which is 1/4 of the DSM resolution. If using a mesh-derived DSM, and you also desire an orthomosaic with maximal detail, set the DEM resolution to 4x your GSD. Set to 0 to use Metashape-determined default. 153 | export: True # Whether to export DEM(s) 154 | tiff_big: True # Use BigTIFF format? Required for larger projects with large DEMs 155 | tiff_tiled: False # Use tiled TIFF? This is related to internal file architecture. 156 | nodata: -32767 # Value used to represent nodata. 157 | tiff_overviews: True # Include coarse-scale raster data in file for quick display in GIS. 158 | 159 | buildOrthomosaic: # (Metashape: buildOrthomosaic, exportRaster) 160 | enabled: True 161 | surface: ["DTM-ptcloud", "DSM-ptcloud", "DSM-mesh", "Mesh"] # Options: "DTM-ptcloud", "DSM-ptcloud", "DSM-mesh", and/or "Mesh". The surface to build the orthomosaic onto. DTM and DSM refer to elevation models built by Metashape and must be configured to be computed via buildDem, above. Mesh refers to using the mesh model directly rather than first computing a DEM. 162 | blending: Metashape.MosaicBlending # Photo blending mode. Options include AverageBlending, MosaicBlending, MinBlending, MaxBlending, DisabledBlending 163 | fill_holes: True # Fill holes in orthomosaic where no photo data exist by interpolating? 164 | refine_seamlines: True # Use smart algorithm to identify photo seamlines where they will least distort. 165 | export: True # Whether to export orthomosaic(s) 166 | tiff_big: True # Use BigTIFF format? Required for larger projects with large DEMs 167 | tiff_tiled: True # Use tiled TIFF? This is related to internal file architecture. Tiled may be (semi-)equivalent to COG. 168 | nodata: -32767 # Value used to represent nodata. 169 | tiff_overviews: True # Include coarse-scale raster data in file for quick display in GIS. 170 | remove_after_export: True # Remove orthomosaic from project after export to reduce the metashape project file size 171 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/R/prep_configs.R: -------------------------------------------------------------------------------- 1 | ### Author: Derek Young, UC Davis 2 | 3 | ### This script does the following: 4 | ### - Takes a base config YAML template and a set of alternate (derived) parameter values (partial YAMLs that only specify the parameters to change) and composes a set of config files to run in the metashape workflow) 5 | 6 | library(yaml) 7 | library(readr) 8 | library(stringr) 9 | 10 | #### Determine paths and read YAML files #### 11 | 12 | # If running manually, specify path to base and derived YAML templates 13 | manual_yaml_path = "/storage/forestuav/configs/set26" 14 | # also the path to metashape repo (this is used only in building the batch job script -- for the call to metashape) 15 | manual_metashape_path = "~/Documents/projects/metashape/python/metashape_workflow.py" 16 | 17 | ## read paths from command line argument (otherwise use the hard-coded defaults above) 18 | command_args = commandArgs(trailingOnly=TRUE) 19 | 20 | if(length(command_args) == 0) { 21 | yaml_path = manual_yaml_path 22 | metashape_path = manual_metashape_path 23 | } else if (length(command_args) == 1) { 24 | yaml_path = command_args[1] 25 | } else { 26 | metashape_path = command_args[2] 27 | yaml_path = command_args[1] 28 | } 29 | 30 | ## Read YAML files 31 | base_yaml_path = paste0(yaml_path,"/","base.yml") 32 | derived_yaml_path = paste0(yaml_path,"/","derived.yml") 33 | 34 | base = read_yaml(base_yaml_path) 35 | 36 | # read derived config lines as vector 37 | derived_data = read_lines(derived_yaml_path, skip_empty_rows = TRUE) 38 | 39 | 40 | 41 | #### Store each derived set as a separate R object (interpreted from YAML) #### 42 | 43 | # remove newlines at start of each line 44 | derived_data = str_replace(derived_data,"^\n","") 45 | 46 | # search for vector elements (lines) that indicate the start of a derived parameter set 47 | start_rows = grep("^####CONFIG", derived_data) 48 | 49 | # Vector to store config file locations to create a shell script 50 | config_files = NULL 51 | 52 | # For each derived parameter set, replace the base parameters with the provided derived parameters, and write a config file 53 | for(i in 1:length(start_rows)) { 54 | 55 | # get first line of the current derived parameter set 56 | first_line = start_rows[i] + 1 57 | 58 | # get last line of the current derived parameter set 59 | if(i != length(start_rows)) { 60 | last_line = start_rows[i+1] - 1 61 | } else { 62 | last_line = length(derived_data) 63 | } 64 | 65 | 66 | ## save as R object 67 | yaml_string = paste(derived_data[first_line:last_line],collapse="\n") 68 | derived_focal = yaml.load(yaml_string) 69 | 70 | ## take the template and replace each element specified in the derived with the value specified in the derived 71 | base_derived = modifyList(base,derived_focal) 72 | 73 | 74 | ## get the number (ID) of the derived set (just use the run name from the YAML) 75 | id = base_derived$run_name 76 | 77 | ## write the derived set with its ID number 78 | filename = paste0(yaml_path,"/cfg_",id,".yml") 79 | 80 | write_yaml(base_derived,filename) 81 | 82 | config_files = c(config_files,filename) 83 | 84 | 85 | } 86 | 87 | 88 | ## make a shell script to run all the config files (assume WD is the metashape repo) 89 | shell_lines = paste0("python ", metashape_path, " ", config_files) 90 | 91 | writeLines(shell_lines, 92 | con = paste0(yaml_path,"/config_batch.sh"), sep="\n") 93 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/R/prep_gcps.R: -------------------------------------------------------------------------------- 1 | ### Author: Derek Young, UC Davis 2 | 3 | ### This script does the following: 4 | ### - Loads a user-created data file of the image files (and coordinates in each image) where GCPs are located 5 | ### - Loads a geospatial file containing the locations of the GCPs 6 | ### - Loads a 10 m DEM and extracts elevation values at each GCP 7 | ### - Compiles all of the above into a file needed by Metashape for its GCP workflow 8 | ### - Produces a PDF that shows each GCP image and the location of the GCP for QAQC 9 | 10 | 11 | ### This script requires the following folders/files in the main mission imagery directory (the one containing 100MEDIA etc): 12 | ### - gcps/raw/gcps.gpkg : geospatial data file with each gcp id in the column "gcp_id". Must be in the same projection as the whole Metashape project, and must be in meters xy (usually a UTM zone). 13 | ### - gcps/raw/gcp_imagecoords.csv : data file listing the images and coordinates in each where a GCP is visible (created manually by technician via imagery inspection) 14 | ### - dem_usgs/dem_usgs.tif : 10 m USGS dem extending well beyond the project flight area 15 | 16 | ### This script assumes all images to be linked to GCPs have standard DJI naming and directory structure ("100MEDIA/DJI_xxxx.JPG"). 17 | ### In input data file gcp_imagecoords.csv, images are specified without "DJI_", leading zeros, and ".JPG". The image directory is specified without "MEDIA" 18 | 19 | 20 | #### Load packages #### 21 | 22 | library(sf) 23 | library(raster) 24 | library(dplyr) 25 | library(stringr) 26 | library(magick) 27 | library(ggplot2) 28 | 29 | #### User-defined vars (only used when running interactivesly) #### 30 | 31 | dir_manual = "/home/derek/Downloads/crater_gcps" 32 | 33 | 34 | 35 | #### Load data #### 36 | 37 | ### All relevant GCP data should be in the top-level mission imagery folder 38 | ### Load folder from the command line argument 39 | 40 | 41 | dir = commandArgs(trailingOnly=TRUE) 42 | 43 | if(length(dir) == 0) { 44 | dir = dir_manual 45 | } 46 | 47 | gcps = read_sf(paste0(dir,"/gcps/raw/gcps.geojson")) 48 | imagecoords = read.csv(paste0(dir,"/gcps/raw/gcp_imagecoords.csv"),header=TRUE,stringsAsFactors=FALSE) 49 | dem_usgs = raster(paste0(dir,"/dem_usgs/dem_usgs.tif")) 50 | 51 | # remove blank lines from image coords file 52 | imagecoords = imagecoords %>% 53 | filter(!is.na(x)) 54 | 55 | 56 | #### Make prepared data directory if it doesn't ecist #### 57 | dir.create(paste0(dir,"/gcps/prepared"),showWarnings=FALSE) 58 | 59 | 60 | 61 | #### Create GCP table in the format required by metashape_control and metashape_functions #### 62 | 63 | # Extract elev 64 | gcp_table = gcps 65 | gcp_table$elev = suppressWarnings(extract(dem_usgs,gcp_table,method="bilinear")) 66 | 67 | # Extract coords 68 | coords = st_coordinates(gcp_table) 69 | gcp_table = cbind(gcp_table,coords) 70 | 71 | # Remove geospatial info 72 | st_geometry(gcp_table) = NULL 73 | 74 | # Reorder columns, add "point" prefix to GCP names 75 | gcp_table = gcp_table %>% 76 | dplyr::select(gcp_id,x=X,y=Y,elev) %>% 77 | dplyr::mutate(gcp_id = paste0("point",gcp_id)) 78 | 79 | write.table(gcp_table,paste0(dir,"/gcps/prepared/gcp_table.csv"),row.names=FALSE,col.names=FALSE,sep=",") 80 | 81 | 82 | #### Create image coordinate-to-gcp table in the format required by metashape_control and metashape_functions #### 83 | 84 | imagecoords_table = imagecoords %>% 85 | mutate(gcp_id = paste0("point",gcp)) %>% 86 | mutate(image_text = paste0("DJI_",str_pad(image_file,4,pad="0"),".JPG")) %>% 87 | mutate(part_text = paste0("PART_",str_pad(part_folder,2,pad="0"))) %>% 88 | mutate(folder_text = paste0(media_folder,"MEDIA")) %>% 89 | mutate(image_path = paste0(part_text,"/",folder_text,"/",image_text)) %>% 90 | dplyr::select(gcp_id,image_path,x,y) %>% 91 | arrange(gcp_id,image_path) 92 | 93 | # remove blank lines from image coords file 94 | imagecoords = imagecoords %>% 95 | filter(!is.na(gcp)) 96 | 97 | 98 | write.table(imagecoords_table,paste0(dir,"/gcps/prepared/gcp_imagecoords_table.csv"),row.names=FALSE,col.names=FALSE,sep=",") 99 | 100 | 101 | #### Export a PDF of images with the GCP circled on each #### 102 | 103 | 104 | pdf(paste0(dir,"/gcps/prepared/gcp_qaqc.pdf")) 105 | 106 | for(i in 1:nrow(imagecoords_table)) { 107 | 108 | imagecoords_row = imagecoords_table[i,] 109 | 110 | img = image_read(paste0(dir,"/",imagecoords_row$image_path)) 111 | img = image_scale(img,"10%") 112 | img = image_flip(img) 113 | 114 | img_x = imagecoords_row$x/10 115 | img_y = imagecoords_row$y/10 116 | img_gcp = imagecoords_row$gcp_id 117 | img_path = imagecoords_row$image_path 118 | 119 | map = image_ggplot(img) + 120 | geom_point(x=img_x,y=img_y,size=20,pch=1,fill=NA,color="red",stroke=1) + 121 | geom_point(x=img_x,y=img_y,size=20,pch=3,fill=NA,color="red",stroke=0.5) + 122 | labs(title=paste0(img_gcp,"\n",img_path)) 123 | 124 | print(map) 125 | 126 | cat("Completed GCP ", i, " of ",nrow(imagecoords_table),"\r") 127 | 128 | } 129 | 130 | garbage = dev.off() 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/config/base.yml: -------------------------------------------------------------------------------- 1 | # This is an example yaml configuration for a metashape run 2 | 3 | #### Project-level parameters: 4 | 5 | # Project to load. If not a blank string, this will open an existing project at the path specified. If a blank string, creates a new empty project. 6 | # Even if opening an existing project, all processing on it is saved as a new project (path and name specified below). The original project file is not modified. 7 | load_project: "" 8 | 9 | # The path to the directory of flight photos 10 | # If there are multiple photo folders, set path to the folder that contains all the photo folders 11 | # If there are no photos to add (e.g., this is an existing project that already has photos in it, set to an empty string ("") 12 | photo_path: "/storage/uav/photosets/sample_rgb_photoset" 13 | multispectral: False # Is this a multispectral photo set? If RGB, set to False. 14 | 15 | # Path for exports (e.g., points, DSM, orthomosaic) and processing log. Will be created if does not exist. 16 | output_path: "/storage/uav/metashape_outputs/sample" 17 | 18 | # Path to save Metashape project file (.psx). Will be created if does not exist 19 | project_path: "/storage/uav/metashape_projects/sample" 20 | 21 | # The identifier for the run. Will be used in naming output files. Recommended to include a photoset name and processing parameter set name. 22 | run_name: "sample_rgb_photoset_run001" 23 | 24 | # CRS EPSG code that project outputs should be in (projection should be in meter units and intended for the project area) 25 | project_crs: "EPSG::26910" # 26910 is UTM 10N 26 | 27 | # Enable metashape "fine-level task subdivision" which reduces memory use by breaking processing into independent chunks that are run in series. 28 | # Assuming there's enough memory, it seems to run 10-20% faster by disabling subdividing. But large projects can run out memory and fail if subdivide is not enabled. 29 | subdivide_task: True 30 | 31 | # Should CUDA GPU driver be used? Alternative is OpenCL. Metashape uses CUDA by default but we have observed it can cause crashes on HPC infrastructure 32 | use_cuda: True # Recommended: True (Metashape default) 33 | 34 | # What value to use for the Metashape tweak "depth_max_gpu_multiplier"? May help to mitigate GPU errors per: https://www.agisoft.com/forum/index.php?topic=11771.0 35 | gpu_multiplier: 2 # Recommended: 2 (Metashape default) 36 | 37 | #### Processing parameters: 38 | ## Steps can be run or skipped using the 'enabled' parameter. If enabled == False, everything else in the step is irrelevant. 39 | ## The metashape functions powering each of these steps are listed in the comments in parentheses. 40 | ## Refer to Metashape documentation for full parameter definitions: https://www.agisoft.com/pdf/metashape_python_api_1_5_0.pdf 41 | ## Parameter names here generally follow the parameter names of the Metashape functions. 42 | 43 | ### Whether to use image EXIF RTK flags to make image geospatial accuracy more precise 44 | use_rtk: True # Recommended: True 45 | fix_accuracy: 3 46 | nofix_accuracy: 25 47 | 48 | # To use GCPs, a 'gcps' folder must exist in the top level photos folder. The contents of the 'gcps' folder are created by the prep_gcps.R script. See readme: https://github.com/ucdavis/metashape 49 | addGCPs: 50 | enabled: True 51 | gcp_crs: "EPSG::26910" # CRS EPSG code of GCP coordinates. 26910 (UTM 10 N) is the CRS of the sample RGB photoset. 52 | marker_location_accuracy: 0.1 # Recommended: 0.1. Accuracy of GCPs real-world coordinates, in meters. 53 | marker_projection_accuracy: 8 # Recommended: 8. Accuracy of the identified locations of the GCPs within the images, in pixels. 54 | optimize_w_gcps_only: True # Optimize alignment using GCPs only: required for GCP locations to take precedence over photo GPS data. Disabling it makes GCPs essentially irrelevant. 55 | 56 | calibrateReflectance: # (Metahsape: calibrateReflectance) 57 | enabled: True 58 | panel_filename: "RP04-1923118-OB.csv" # The calibration file must be in the "calibration" folder in the top-level project photos directory. See example panel calibration file in the calibration directory of project repo. 59 | use_reflectance_panels: True 60 | use_sun_sensor: True 61 | 62 | alignPhotos: # (Metashape: alignPhotos) 63 | enabled: True 64 | downscale: 2 # Recommended: 2. How much to coarsen the photos when searching for tie points. Higher number for blurrier photos or when there are small surfces that may move between photos (such as leaves). Accepts numbers 2^x (and zero) (https://www.agisoft.com/forum/index.php?topic=11697.0). 65 | adaptive_fitting: True # Recommended: True. Should the camera lens model be fit at the same time as aligning photos? 66 | keep_keypoints: True # Recommended: True. Should keypoints from matching photos be stored in the project? Required if you later want to add more photos and align them to the previously aligned photos without redoing the original alignment. 67 | reset_alignment: False # Recommended: False. When running an alignment, if any of the photos were already aligned, should we keep that alignment? Or reset it so we align everything anew? 68 | 69 | filterPointsUSGS: 70 | enabled: False 71 | rec_thresh_percent: 20 72 | rec_thresh_absolute: 15 73 | proj_thresh_percent: 30 74 | proj_thresh_absolute: 2 75 | reproj_thresh_percent: 5 76 | reproj_thresh_absolute: 0.3 77 | 78 | optimizeCameras: # (Metashape: optimizeCameras) 79 | enabled: True 80 | adaptive_fitting: True # Recommended: True. Should the camera lens model be fit at the same time as optinizing photos? 81 | 82 | buildDenseCloud: # (Metashape: buildDepthMaps, buildDenseCloud, (optionally) classifyGroundPoints, and exportPoints) 83 | enabled: True 84 | ## For depth maps (buldDepthMaps) 85 | downscale: 2 # Recommended: 2. How much to coarsen the photos when searching for matches to build the dense cloud. For large photosets, values < 4 likely take prohibitively long. Accepts numbers 2^x (https://www.agisoft.com/forum/index.php?topic=11697.0). 86 | filter_mode: Metashape.MildFiltering # Recommended: Metashape.MildFiltering. How to filter the point cloud. Options are NoFiltering, MildFiltering, ModerateFiltering, AggressiveFiltering. Aggressive filtering removes detail and makes worse DEMs (at least for forest). NoFiltering takes very long. In trials, it never completed. 87 | reuse_depth: False # Recommended: False. Purpose unknown. 88 | ## For dense cloud (buildDenseCloud) 89 | keep_depth: False # Recommended: False. Purpose unknown. 90 | ## For both 91 | max_neighbors: 100 # Recommended: 100. Maximum number of neighboring photos to use for estimating point cloud. Higher numbers may increase accuracy but dramatically increase processing time. 92 | ## For ground point classification (classifyGroundPoints). Definitions here: https://www.agisoft.com/forum/index.php?topic=9328.0 93 | classify_ground_points: False # Should ground points be classified as a part of this step? Must be enabled (either here or in buldDem, below) if a digital terrain model (DTM) is needed either for orthomosaic or DTM export. Enabling here is an alternative to enabling as a component of buildDem (below). It depends on which stage you want the classification to be done at. If you already have a point cloud but it's unclassified, then don't do it as part of this stage as it would require computing the point cloud again. 94 | ## For dense cloud export (exportPoints) 95 | export: True # Whether to export dense cloud file. 96 | format: Metashape.PointsFormatLAS # Recommended: PointsFormatLAS. The file format to export points in. 97 | classes: "ALL" # Recommended: "ALL". Point classes to export. Must be a list. Or can set to "ALL" to use all points. An example of a specific class is: Metashape.PointClass.Ground 98 | 99 | classifyGroundPoints: # (Meatshape: classifyGroundPoints) # classify points, IF SPECIFIED as a component of buildDenseCloud (above) or buldDem (below). Must be enabled (either here or in buldDem, below) if a digital terrain model (DTM) is needed either for orthomosaic or DTM export. 100 | max_angle: 15.0 # Recommended: 15.0 101 | max_distance: 1.0 # Recommended: 1.0 102 | cell_size: 50.0 # Recommended: 50.0 103 | 104 | buildDem: # (Metashape: buildDem, (optionally) classifyGroundPoints, exportRaster) 105 | enabled: True 106 | classify_ground_points: True # Should ground points be classified as part of this step? Note that an alternative is to calculate them as a part of buildDenseCloud (above) 107 | ## For building DEM (buildDem) 108 | type: "both" # Recommended: "both". Options: "DSM" or "DTM" or "both". Type of DEM to exporot (digital surface model, digital terrain model, or both). 109 | ## For exporting DEM (exportRaster) 110 | export: True # Whether to export DEM(s) 111 | tiff_big: True # Recommended: True. Use BigTIFF format? Required for larger projects with large DEMs 112 | tiff_tiled: False # Recommended: False. Use tiled TIFF? This is related to internal file architecture. 113 | nodata: -32767 # Recommended: -32767. Value used to represent nodata. 114 | tiff_overviews: True # Recommended: True. Include coarse-scale raster data in file for quick display in GIS. 115 | 116 | buildOrthomosaic: # (Metashape: buildOrthomosaic, exportRaster) 117 | enabled: True 118 | ## For building orthomosaic (buildOrthomosaic) 119 | surface: "USGS" # Recommended: "USGS" (assuming a USGS DEM is available and GCPs with accurate elvevation data are being used). The surface to build the orthomosaic onto. "DTM", "DSM", "USGS", or "DTMandDSM. DTM and DSM refer to elevation models built by Metashape (buildDem step above) and stored in the project. If USGS, you must use GCPs with accurate elevations (ideally extracted from the USGS DEM). 120 | usgs_dem_path: "dem_usgs/dem_usgs.tif" # Path to USGS DEM for the project area. Needed if surface (parameter above) is "USGS". 121 | usgs_dem_crs: "EPSG::4269" # CRS of the USGS DEM. Needed if surface (parameter above) is "USGS". For sample RGB photoset, crs is 4269 (Geographic NAD83) 122 | blending: Metashape.MosaicBlending # Recommended: Metashape.MosaicBlending. Photo blending mode. Options include AverageBlending, MosaicBlending, MinBlending, MaxBlending, DisabledBlending 123 | fill_holes: True # Recommended: True. Fill holes in orthomosaic where no photo data exist by interpolating? 124 | refine_seamlines: True # Recommended: True. Use smart algorithm to identify photo seamlines where they will least distort. 125 | ## For exporting orthomosaic (exportRaster) 126 | export: True # Whether to export orthomosaic 127 | tiff_big: True # Recommended: True. Use BigTIFF format? Required for larger projects with large DEMs 128 | tiff_tiled: False # Recommended: False. Use tiled TIFF? This is related to internal file architecture. 129 | nodata: -32767 # Recommended: -32767. Value used to represent nodata. 130 | tiff_overviews: True # Recommended: True. Include coarse-scale raster data in file for quick display in GIS. 131 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/config/derived.yml: -------------------------------------------------------------------------------- 1 | ####CONFIG_0001#### 2 | photo_path: "/storage/uav/photosets/sample_rgb_photoset_01" 3 | multispectral: False 4 | buildDenseCloud: # (Metashape: buildDepthMaps, buildDenseCloud, classifyGroundPoints, and exportPoints) 5 | enabled: False 6 | 7 | ####CONFIG_0002#### 8 | photo_path: "/storage/uav/photosets/sample_rgb_photoset_02" 9 | multispectral: True 10 | 11 | ####CONFIG_0003#### 12 | photo_path: "/storage/uav/photosets/sample_rgb_photoset_03" 13 | multispectral: False 14 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/config/example.yml: -------------------------------------------------------------------------------- 1 | # This is an example yaml configuration for a metashape run 2 | 3 | #### Project-level parameters: 4 | 5 | # Project to load. If not a blank string, this will open an existing project at the path specified. If a blank string, creates a new empty project. 6 | # Even if opening an existing project, all processing on it is saved as a new project (path and name specified below). The original project file is not modified. 7 | load_project: "" 8 | 9 | # The path to the directory of flight photos 10 | # If there are multiple photo folders, set path to the folder that contains all the photo folders 11 | # If there are no photos to add (e.g., this is an existing project that already has photos in it, set to an empty string ("") 12 | photo_path: "/storage/uav/photosets/sample_rgb_photoset" 13 | multispectral: False # Is this a multispectral photo set? If RGB, set to False. 14 | 15 | # Path for exports (e.g., points, DSM, orthomosaic) and processing log. Will be created if does not exist. 16 | output_path: "/storage/uav/metashape_outputs/sample" 17 | 18 | # Path to save Metashape project file (.psx). Will be created if does not exist 19 | project_path: "/storage/uav/metashape_projects/sample" 20 | 21 | # The identifier for the run. Will be used in naming output files. Recommended to include a photoset name and processing parameter set name. 22 | run_name: "sample_rgb_photoset_run001" 23 | 24 | # CRS EPSG code that project outputs should be in (projection should be in meter units and intended for the project area) 25 | project_crs: "EPSG::26910" # 26910 is UTM 10N 26 | 27 | # Enable metashape "fine-level task subdivision" which reduces memory use by breaking processing into independent chunks that are run in series. 28 | # Assuming there's enough memory, it seems to run 10-20% faster by disabling subdividing. But large projects can run out memory and fail if subdivide is not enabled. 29 | subdivide_task: True 30 | 31 | # Should CUDA GPU driver be used? Alternative is OpenCL. Metashape uses CUDA by default but we have observed it can cause crashes on HPC infrastructure 32 | use_cuda: True # Recommended: True (Metashape default) 33 | 34 | # What value to use for the Metashape tweak "depth_max_gpu_multiplier"? May help to mitigate GPU errors per: https://www.agisoft.com/forum/index.php?topic=11771.0 35 | gpu_multiplier: 2 # Recommended: 2 (Metashape default) 36 | 37 | #### Processing parameters: 38 | ## Steps can be run or skipped using the 'enabled' parameter. If enabled == False, everything else in the step is irrelevant. 39 | ## The metashape functions powering each of these steps are listed in the comments in parentheses. 40 | ## Refer to Metashape documentation for full parameter definitions: https://www.agisoft.com/pdf/metashape_python_api_1_5_0.pdf 41 | ## Parameter names here generally follow the parameter names of the Metashape functions. 42 | 43 | ### Whether to use image EXIF RTK flags to make image geospatial accuracy more precise 44 | use_rtk: True # Recommended: True 45 | fix_accuracy: 3 46 | nofix_accuracy: 25 47 | 48 | # To use GCPs, a 'gcps' folder must exist in the top level photos folder. The contents of the 'gcps' folder are created by the prep_gcps.R script. See readme: https://github.com/ucdavis/metashape 49 | addGCPs: 50 | enabled: True 51 | gcp_crs: "EPSG::26910" # CRS EPSG code of GCP coordinates. 26910 (UTM 10 N) is the CRS of the sample RGB photoset. 52 | marker_location_accuracy: 0.1 # Recommended: 0.1. Accuracy of GCPs real-world coordinates, in meters. 53 | marker_projection_accuracy: 8 # Recommended: 8. Accuracy of the identified locations of the GCPs within the images, in pixels. 54 | optimize_w_gcps_only: True # Optimize alignment using GCPs only: required for GCP locations to take precedence over photo GPS data. Disabling it makes GCPs essentially irrelevant. 55 | 56 | calibrateReflectance: # (Metahsape: calibrateReflectance) 57 | enabled: True 58 | panel_filename: "RP04-1923118-OB.csv" # The calibration file must be in the "calibration" folder in the top-level project photos directory. See example panel calibration file in the calibration directory of project repo. 59 | use_reflectance_panels: True 60 | use_sun_sensor: True 61 | 62 | alignPhotos: # (Metashape: alignPhotos) 63 | enabled: True 64 | downscale: 2 # Recommended: 2. How much to coarsen the photos when searching for tie points. Higher number for blurrier photos or when there are small surfces that may move between photos (such as leaves). Accepts numbers 2^x (and zero) (https://www.agisoft.com/forum/index.php?topic=11697.0). 65 | adaptive_fitting: True # Recommended: True. Should the camera lens model be fit at the same time as aligning photos? 66 | keep_keypoints: True # Recommended: True. Should keypoints from matching photos be stored in the project? Required if you later want to add more photos and align them to the previously aligned photos without redoing the original alignment. 67 | reset_alignment: False # Recommended: False. When running an alignment, if any of the photos were already aligned, should we keep that alignment? Or reset it so we align everything anew? 68 | 69 | filterPointsUSGS: 70 | enabled: False 71 | rec_thresh_percent: 20 72 | rec_thresh_absolute: 15 73 | proj_thresh_percent: 30 74 | proj_thresh_absolute: 2 75 | reproj_thresh_percent: 5 76 | reproj_thresh_absolute: 0.3 77 | 78 | optimizeCameras: # (Metashape: optimizeCameras) 79 | enabled: True 80 | adaptive_fitting: True # Recommended: True. Should the camera lens model be fit at the same time as optinizing photos? 81 | 82 | buildDenseCloud: # (Metashape: buildDepthMaps, buildDenseCloud, (optionally) classifyGroundPoints, and exportPoints) 83 | enabled: True 84 | ## For depth maps (buldDepthMaps) 85 | downscale: 2 # Recommended: 2. How much to coarsen the photos when searching for matches to build the dense cloud. For large photosets, values < 4 likely take prohibitively long. Accepts numbers 2^x (https://www.agisoft.com/forum/index.php?topic=11697.0). 86 | filter_mode: Metashape.MildFiltering # Recommended: Metashape.MildFiltering. How to filter the point cloud. Options are NoFiltering, MildFiltering, ModerateFiltering, AggressiveFiltering. Aggressive filtering removes detail and makes worse DEMs (at least for forest). NoFiltering takes very long. In trials, it never completed. 87 | reuse_depth: False # Recommended: False. Purpose unknown. 88 | ## For dense cloud (buildDenseCloud) 89 | keep_depth: False # Recommended: False. Purpose unknown. 90 | ## For both 91 | max_neighbors: 100 # Recommended: 100. Maximum number of neighboring photos to use for estimating point cloud. Higher numbers may increase accuracy but dramatically increase processing time. 92 | ## For ground point classification (classifyGroundPoints). Definitions here: https://www.agisoft.com/forum/index.php?topic=9328.0 93 | classify_ground_points: False # Should ground points be classified as a part of this step? Must be enabled (either here or in buldDem, below) if a digital terrain model (DTM) is needed either for orthomosaic or DTM export. Enabling here is an alternative to enabling as a component of buildDem (below). It depends on which stage you want the classification to be done at. If you already have a point cloud but it's unclassified, then don't do it as part of this stage as it would require computing the point cloud again. 94 | ## For dense cloud export (exportPoints) 95 | export: True # Whether to export dense cloud file. 96 | format: Metashape.PointsFormatLAS # Recommended: PointsFormatLAS. The file format to export points in. 97 | classes: "ALL" # Recommended: "ALL". Point classes to export. Must be a list. Or can set to "ALL" to use all points. An example of a specific class is: Metashape.PointClass.Ground 98 | 99 | classifyGroundPoints: # (Meatshape: classifyGroundPoints) # classify points, IF SPECIFIED as a component of buildDenseCloud (above) or buldDem (below). Must be enabled (either here or in buldDem, below) if a digital terrain model (DTM) is needed either for orthomosaic or DTM export. 100 | max_angle: 15.0 # Recommended: 15.0 101 | max_distance: 1.0 # Recommended: 1.0 102 | cell_size: 50.0 # Recommended: 50.0 103 | 104 | buildDem: # (Metashape: buildDem, (optionally) classifyGroundPoints, exportRaster) 105 | enabled: True 106 | classify_ground_points: True # Should ground points be classified as part of this step? Note that an alternative is to calculate them as a part of buildDenseCloud (above) 107 | ## For building DEM (buildDem) 108 | type: "both" # Recommended: "both". Options: "DSM" or "DTM" or "both". Type of DEM to exporot (digital surface model, digital terrain model, or both). 109 | ## For exporting DEM (exportRaster) 110 | export: True # Whether to export DEM(s) 111 | tiff_big: True # Recommended: True. Use BigTIFF format? Required for larger projects with large DEMs 112 | tiff_tiled: False # Recommended: False. Use tiled TIFF? This is related to internal file architecture. 113 | nodata: -32767 # Recommended: -32767. Value used to represent nodata. 114 | tiff_overviews: True # Recommended: True. Include coarse-scale raster data in file for quick display in GIS. 115 | 116 | buildOrthomosaic: # (Metashape: buildOrthomosaic, exportRaster) 117 | enabled: True 118 | ## For building orthomosaic (buildOrthomosaic) 119 | surface: "USGS" # Recommended: "USGS" (assuming a USGS DEM is available and GCPs with accurate elvevation data are being used). The surface to build the orthomosaic onto. "DTM", "DSM", "USGS", or "DTMandDSM. DTM and DSM refer to elevation models built by Metashape (buildDem step above) and stored in the project. If USGS, you must use GCPs with accurate elevations (ideally extracted from the USGS DEM). 120 | usgs_dem_path: "dem_usgs/dem_usgs.tif" # Path to USGS DEM for the project area. Needed if surface (parameter above) is "USGS". 121 | usgs_dem_crs: "EPSG::4269" # CRS of the USGS DEM. Needed if surface (parameter above) is "USGS". For sample RGB photoset, crs is 4269 (Geographic NAD83) 122 | blending: Metashape.MosaicBlending # Recommended: Metashape.MosaicBlending. Photo blending mode. Options include AverageBlending, MosaicBlending, MinBlending, MaxBlending, DisabledBlending 123 | fill_holes: True # Recommended: True. Fill holes in orthomosaic where no photo data exist by interpolating? 124 | refine_seamlines: True # Recommended: True. Use smart algorithm to identify photo seamlines where they will least distort. 125 | ## For exporting orthomosaic (exportRaster) 126 | export: True # Whether to export orthomosaic 127 | tiff_big: True # Recommended: True. Use BigTIFF format? Required for larger projects with large DEMs 128 | tiff_tiled: False # Recommended: False. Use tiled TIFF? This is related to internal file architecture. 129 | nodata: -32767 # Recommended: -32767. Value used to represent nodata. 130 | tiff_overviews: True # Recommended: True. Include coarse-scale raster data in file for quick display in GIS. 131 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/python/metashape_workflow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # File for running a metashape workflow 3 | 4 | # Derek Young and Alex Mandel 5 | # University of California, Davis 6 | # 2021 7 | 8 | import sys 9 | 10 | # ---- If this is a first run from the standalone python module, need to copy the license file from the full metashape install: from python import metashape_license_setup 11 | 12 | ## Define where to get the config file (only used if running interactively) 13 | manual_config_file = "config/example_dev.yml" 14 | # ---- If not running interactively, the config file should be supplied as the command-line argument after the python script, e.g.: python metashape_workflow.py config.yml 15 | 16 | 17 | ## Load custom modules and config file: slightly different depending whether running interactively or via command line 18 | try: # running interactively (in linux) or command line (windows) 19 | from python import metashape_workflow_functions as meta 20 | from python import read_yaml 21 | except: # running from command line (in linux) or interactively (windows) 22 | import metashape_workflow_functions as meta 23 | import read_yaml 24 | 25 | if sys.stdin.isatty(): 26 | config_file = sys.argv[1] 27 | else: 28 | config_file = manual_config_file 29 | 30 | ## Parse the config file 31 | cfg = read_yaml.read_yaml(config_file) 32 | 33 | ### Run the Metashape workflow 34 | 35 | doc, log, run_id = meta.project_setup(cfg) 36 | 37 | meta.enable_and_log_gpu(log, cfg) 38 | 39 | if cfg["photo_path"] != "": # only add photos if there is a photo directory listed 40 | meta.add_photos(doc, cfg) 41 | 42 | if cfg["calibrateReflectance"]["enabled"]: 43 | meta.calibrate_reflectance(doc, cfg) 44 | 45 | if cfg["alignPhotos"]["enabled"]: 46 | meta.align_photos(doc, log, cfg) 47 | meta.reset_region(doc) 48 | 49 | if cfg["filterPointsUSGS"]["enabled"]: 50 | meta.filter_points_usgs_part1(doc, cfg) 51 | meta.reset_region(doc) 52 | 53 | if cfg["addGCPs"]["enabled"]: 54 | meta.add_gcps(doc, cfg) 55 | meta.reset_region(doc) 56 | 57 | if cfg["optimizeCameras"]["enabled"]: 58 | meta.optimize_cameras(doc, cfg) 59 | meta.reset_region(doc) 60 | 61 | if cfg["filterPointsUSGS"]["enabled"]: 62 | meta.filter_points_usgs_part2(doc, cfg) 63 | meta.reset_region(doc) 64 | 65 | if cfg["buildDenseCloud"]["enabled"]: 66 | meta.build_dense_cloud(doc, log, run_id, cfg) 67 | 68 | if cfg["buildDem"]["enabled"]: 69 | meta.build_dem(doc, log, run_id, cfg) 70 | 71 | if cfg["buildOrthomosaic"]["enabled"]: 72 | meta.build_orthomosaics(doc, log, run_id, cfg) 73 | 74 | meta.export_report(doc, run_id, cfg) 75 | 76 | meta.finish_run(log, config_file) 77 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/python/metashape_workflow_functions.py: -------------------------------------------------------------------------------- 1 | # Derek Young and Alex Mandel 2 | # University of California, Davis 3 | # 2021 4 | 5 | #### Import libraries 6 | 7 | import datetime 8 | import glob 9 | import os 10 | import platform 11 | import re 12 | 13 | # import the fuctionality we need to make time stamps to measure performance 14 | import time 15 | 16 | ### import the Metashape functionality 17 | import Metashape 18 | import yaml 19 | 20 | #### Helper functions and globals 21 | 22 | # Set the log file name-value separator 23 | # Chose ; as : is in timestamps 24 | # TODO: Consider moving log to json/yaml formatting using a dict 25 | sep = "; " 26 | 27 | 28 | def stamp_time(): 29 | """ 30 | Format the timestamps as needed 31 | """ 32 | stamp = datetime.datetime.now().strftime("%Y%m%dT%H%M") 33 | return stamp 34 | 35 | 36 | def diff_time(t2, t1): 37 | """ 38 | Give a end and start time, subtract, and round 39 | """ 40 | total = str(round(t2 - t1, 1)) 41 | return total 42 | 43 | 44 | # Used by add_gcps function 45 | def get_marker(chunk, label): 46 | for marker in chunk.markers: 47 | if marker.label == label: 48 | return marker 49 | return None 50 | 51 | 52 | # Used by add_gcps function 53 | def get_camera(chunk, label): 54 | for camera in chunk.cameras: 55 | if camera.label.lower() == label.lower(): 56 | return camera 57 | return None 58 | 59 | 60 | #### Functions for each major step in Metashape 61 | 62 | 63 | def project_setup(cfg): 64 | """ 65 | Create output and project paths, if they don't exist 66 | Define a project ID based on photoset name and timestamp 67 | Define a project filename and a log filename 68 | Create the project 69 | Start a log file 70 | """ 71 | 72 | # Make project directories (necessary even if loading an existing project because this workflow saves a new project based on the old one, leaving the old one intact 73 | if not os.path.exists(cfg["output_path"]): 74 | os.makedirs(cfg["output_path"]) 75 | if not os.path.exists(cfg["project_path"]): 76 | os.makedirs(cfg["project_path"]) 77 | 78 | ### Set a filename template for project files and output files 79 | ## Get the first parts of the filename (the photoset ID and location string) 80 | 81 | run_name = cfg["run_name"] 82 | 83 | ## Project file example to make: "projectID_YYYYMMDDtHHMM-jobID.psx" 84 | timestamp = stamp_time() 85 | run_id = "_".join([run_name, timestamp]) 86 | # TODO: If there is a slurm JobID, append to time (separated with "-", not "_"). This will keep jobs initiated in the same minute distinct 87 | 88 | project_file = os.path.join(cfg["project_path"], ".".join([run_id, "psx"])) 89 | log_file = os.path.join(cfg["output_path"], ".".join([run_id + "_log", "txt"])) 90 | 91 | """ 92 | Create a doc and a chunk 93 | """ 94 | 95 | # create a handle to the Metashape object 96 | doc = ( 97 | Metashape.Document() 98 | ) # When running via Metashape, can use: doc = Metashape.app.document 99 | 100 | # If specified, open existing project 101 | if cfg["load_project"] != "": 102 | doc.open(cfg["load_project"]) 103 | else: 104 | # Initialize a chunk, set its CRS as specified 105 | chunk = doc.addChunk() 106 | chunk.crs = Metashape.CoordinateSystem(cfg["project_crs"]) 107 | chunk.marker_crs = Metashape.CoordinateSystem(cfg["addGCPs"]["gcp_crs"]) 108 | 109 | # Save doc doc as new project (even if we opened an existing project, save as a separate one so the existing project remains accessible in its original state) 110 | doc.save(project_file) 111 | 112 | """ 113 | Log specs except for GPU 114 | """ 115 | 116 | # log Metashape version, CPU specs, time, and project location to results file 117 | # open the results file 118 | # TODO: records the Slurm values for actual cpus and ram allocated 119 | # https://slurm.schedmd.com/sbatch.html#lbAI 120 | with open(log_file, "a") as file: 121 | 122 | # write a line with the Metashape version 123 | file.write(sep.join(["Project", run_id]) + "\n") 124 | file.write( 125 | sep.join(["Agisoft Metashape Professional Version", Metashape.app.version]) 126 | + "\n" 127 | ) 128 | # write a line with the date and time 129 | file.write(sep.join(["Processing started", stamp_time()]) + "\n") 130 | # write a line with CPU info - if possible, improve the way the CPU info is found / recorded 131 | file.write(sep.join(["Node", platform.node()]) + "\n") 132 | file.write(sep.join(["CPU", platform.processor()]) + "\n") 133 | # write two lines with GPU info: count and model names - this takes multiple steps to make it look clean in the end 134 | 135 | return doc, log_file, run_id 136 | 137 | 138 | def enable_and_log_gpu(log_file, cfg): 139 | """ 140 | Enables GPU and logs GPU specs 141 | """ 142 | 143 | gpustringraw = str(Metashape.app.enumGPUDevices()) 144 | gpucount = gpustringraw.count("name': '") 145 | gpustring = "" 146 | currentgpu = 1 147 | while gpucount >= currentgpu: 148 | if gpustring != "": 149 | gpustring = gpustring + ", " 150 | gpustring = ( 151 | gpustring + gpustringraw.split("name': '")[currentgpu].split("',")[0] 152 | ) 153 | currentgpu = currentgpu + 1 154 | # gpustring = gpustringraw.split("name': '")[1].split("',")[0] 155 | gpu_mask = Metashape.app.gpu_mask 156 | 157 | with open(log_file, "a") as file: 158 | file.write(sep.join(["Number of GPUs Found", str(gpucount)]) + "\n") 159 | file.write(sep.join(["GPU Model", gpustring]) + "\n") 160 | file.write(sep.join(["GPU Mask", str(gpu_mask)]) + "\n") 161 | 162 | # If a GPU exists but is not enabled, enable the 1st one 163 | if (gpucount > 0) and (gpu_mask == 0): 164 | Metashape.app.gpu_mask = 1 165 | gpu_mask = Metashape.app.gpu_mask 166 | file.write(sep.join(["GPU Mask Enabled", str(gpu_mask)]) + "\n") 167 | 168 | # This writes down all the GPU devices available 169 | # file.write('GPU(s): '+str(Metashape.app.enumGPUDevices())+'\n') 170 | 171 | # set Metashape to *not* use the CPU during GPU steps (appears to be standard wisdom) 172 | Metashape.app.cpu_enable = False 173 | 174 | # Disable CUDA if specified 175 | if not cfg["use_cuda"]: 176 | Metashape.app.settings.setValue("main/gpu_enable_cuda", "0") 177 | 178 | # Set GPU multiplier to value specified (2 is default) 179 | Metashape.app.settings.setValue( 180 | "main/depth_max_gpu_multiplier", cfg["gpu_multiplier"] 181 | ) 182 | 183 | return True 184 | 185 | 186 | def add_photos(doc, cfg): 187 | """ 188 | Add photos to project and change their labels to include their containing folder 189 | """ 190 | 191 | ## Get paths to all the project photos 192 | a = glob.iglob( 193 | os.path.join(cfg["photo_path"], "**", "*.*"), recursive=True 194 | ) # (([jJ][pP][gG])|([tT][iI][fF])) 195 | b = [path for path in a] 196 | photo_files = [ 197 | x 198 | for x in b 199 | if ( 200 | re.search("(.tif$)|(.jpg$)|(.TIF$)|(.JPG$)", x) 201 | and (not re.search("dem_usgs.tif", x)) 202 | ) 203 | ] 204 | 205 | ## Add them 206 | if cfg["multispectral"]: 207 | doc.chunk.addPhotos(photo_files, layout=Metashape.MultiplaneLayout) 208 | else: 209 | doc.chunk.addPhotos(photo_files) 210 | 211 | ## Need to change the label on each camera so that it includes the containing folder(S) 212 | for camera in doc.chunk.cameras: 213 | path = camera.photo.path 214 | # remove the base imagery dir from this string 215 | rel_path = path.replace(cfg["photo_path"], "") 216 | # if it starts with a '/', remove it 217 | newlabel = re.sub("^/", "", rel_path) 218 | camera.label = newlabel 219 | 220 | ## If specified, change the accuracy of the cameras to match the RTK flag (RTK fix if flag = 50, otherwise no fix 221 | if cfg["use_rtk"]: 222 | for cam in doc.chunk.cameras: 223 | rtkflag = cam.photo.meta["DJI/RtkFlag"] 224 | if rtkflag == "50": 225 | cam.reference.location_accuracy = Metashape.Vector( 226 | [cfg["fix_accuracy"], cfg["fix_accuracy"], cfg["fix_accuracy"]] 227 | ) 228 | cam.reference.accuracy = Metashape.Vector( 229 | [cfg["fix_accuracy"], cfg["fix_accuracy"], cfg["fix_accuracy"]] 230 | ) 231 | else: 232 | cam.reference.location_accuracy = Metashape.Vector( 233 | [ 234 | cfg["nofix_accuracy"], 235 | cfg["nofix_accuracy"], 236 | cfg["nofix_accuracy"], 237 | ] 238 | ) 239 | cam.reference.accuracy = Metashape.Vector( 240 | [ 241 | cfg["nofix_accuracy"], 242 | cfg["nofix_accuracy"], 243 | cfg["nofix_accuracy"], 244 | ] 245 | ) 246 | 247 | doc.save() 248 | 249 | return True 250 | 251 | 252 | def calibrate_reflectance(doc, cfg): 253 | # TODO: Handle failure to find panels, or mulitple panel images by returning error to user. 254 | doc.chunk.locateReflectancePanels() 255 | doc.chunk.loadReflectancePanelCalibration( 256 | os.path.join( 257 | cfg["photo_path"], 258 | "calibration", 259 | cfg["calibrateReflectance"]["panel_filename"], 260 | ) 261 | ) 262 | # doc.chunk.calibrateReflectance(use_reflectance_panels=True,use_sun_sensor=True) 263 | doc.chunk.calibrateReflectance( 264 | use_reflectance_panels=cfg["calibrateReflectance"]["use_reflectance_panels"], 265 | use_sun_sensor=cfg["calibrateReflectance"]["use_sun_sensor"], 266 | ) 267 | doc.save() 268 | 269 | return True 270 | 271 | 272 | def add_gcps(doc, cfg): 273 | """ 274 | Add GCPs (GCP coordinates and the locations of GCPs in individual photos. 275 | See the helper script (and the comments therein) for details on how to prepare the data needed by this function: R/prep_gcps.R 276 | """ 277 | 278 | ## Tag specific pixels in specific images where GCPs are located 279 | path = os.path.join( 280 | cfg["photo_path"], "gcps", "prepared", "gcp_imagecoords_table.csv" 281 | ) 282 | file = open(path) 283 | content = file.read().splitlines() 284 | 285 | for line in content: 286 | marker_label, camera_label, x_proj, y_proj = line.split(",") 287 | if ( 288 | marker_label[0] == '"' 289 | ): # if it's in quotes (from saving CSV in Excel), remove quotes 290 | marker_label = marker_label[ 291 | 1:-1 292 | ] # need to get it out of the two pairs of quotes 293 | if ( 294 | camera_label[0] == '"' 295 | ): # if it's in quotes (from saving CSV in Excel), remove quotes 296 | camera_label = camera_label[1:-1] 297 | 298 | marker = get_marker(doc.chunk, marker_label) 299 | if not marker: 300 | marker = doc.chunk.addMarker() 301 | marker.label = marker_label 302 | 303 | camera = get_camera(doc.chunk, camera_label) 304 | if not camera: 305 | print(camera_label + " camera not found in project") 306 | continue 307 | 308 | marker.projections[camera] = Metashape.Marker.Projection( 309 | (float(x_proj), float(y_proj)), True 310 | ) 311 | 312 | ## Assign real-world coordinates to each GCP 313 | path = os.path.join(cfg["photo_path"], "gcps", "prepared", "gcp_table.csv") 314 | 315 | file = open(path) 316 | content = file.read().splitlines() 317 | 318 | for line in content: 319 | marker_label, world_x, world_y, world_z = line.split(",") 320 | if ( 321 | marker_label[0] == '"' 322 | ): # if it's in quotes (from saving CSV in Excel), remove quotes 323 | marker_label = marker_label[ 324 | 1:-1 325 | ] # need to get it out of the two pairs of quotes 326 | 327 | marker = get_marker(doc.chunk, marker_label) 328 | if not marker: 329 | marker = doc.chunk.addMarker() 330 | marker.label = marker_label 331 | 332 | marker.reference.location = (float(world_x), float(world_y), float(world_z)) 333 | marker.reference.accuracy = ( 334 | cfg["addGCPs"]["marker_location_accuracy"], 335 | cfg["addGCPs"]["marker_location_accuracy"], 336 | cfg["addGCPs"]["marker_location_accuracy"], 337 | ) 338 | 339 | doc.chunk.marker_location_accuracy = ( 340 | cfg["addGCPs"]["marker_location_accuracy"], 341 | cfg["addGCPs"]["marker_location_accuracy"], 342 | cfg["addGCPs"]["marker_location_accuracy"], 343 | ) 344 | doc.chunk.marker_projection_accuracy = cfg["addGCPs"]["marker_projection_accuracy"] 345 | 346 | doc.save() 347 | 348 | return True 349 | 350 | 351 | def align_photos(doc, log_file, cfg): 352 | """ 353 | Match photos, align cameras, optimize cameras 354 | """ 355 | 356 | #### Align photos 357 | 358 | # get a beginning time stamp 359 | timer1a = time.time() 360 | 361 | # Align cameras 362 | doc.chunk.matchPhotos( 363 | downscale=cfg["alignPhotos"]["downscale"], 364 | subdivide_task=cfg["subdivide_task"], 365 | keep_keypoints=cfg["alignPhotos"]["keep_keypoints"], 366 | ) 367 | doc.chunk.alignCameras( 368 | adaptive_fitting=cfg["alignPhotos"]["adaptive_fitting"], 369 | subdivide_task=cfg["subdivide_task"], 370 | reset_alignment=cfg["alignPhotos"]["reset_alignment"], 371 | ) 372 | doc.save() 373 | 374 | # get an ending time stamp 375 | timer1b = time.time() 376 | 377 | # calculate difference between end and start time to 1 decimal place 378 | time1 = diff_time(timer1b, timer1a) 379 | 380 | # record results to file 381 | with open(log_file, "a") as file: 382 | file.write(sep.join(["Align Photos", time1]) + "\n") 383 | 384 | return True 385 | 386 | 387 | def reset_region(doc): 388 | """ 389 | Reset the region and make it much larger than the points; necessary because if points go outside the region, they get clipped when saving 390 | """ 391 | 392 | doc.chunk.resetRegion() 393 | region_dims = doc.chunk.region.size 394 | region_dims[2] *= 3 395 | doc.chunk.region.size = region_dims 396 | 397 | return True 398 | 399 | 400 | def optimize_cameras(doc, cfg): 401 | """ 402 | Optimize cameras 403 | """ 404 | 405 | # Disable camera locations as reference if specified in YML 406 | if cfg["addGCPs"]["enabled"] and cfg["addGCPs"]["optimize_w_gcps_only"]: 407 | n_cameras = len(doc.chunk.cameras) 408 | for i in range(0, n_cameras): 409 | doc.chunk.cameras[i].reference.enabled = False 410 | 411 | # Currently only optimizes the default parameters, which is not all possible parameters 412 | doc.chunk.optimizeCameras( 413 | adaptive_fitting=cfg["optimizeCameras"]["adaptive_fitting"] 414 | ) 415 | doc.save() 416 | 417 | return True 418 | 419 | 420 | def filter_points_usgs_part1(doc, cfg): 421 | 422 | doc.chunk.optimizeCameras( 423 | adaptive_fitting=cfg["optimizeCameras"]["adaptive_fitting"] 424 | ) 425 | 426 | rec_thresh_percent = cfg["filterPointsUSGS"]["rec_thresh_percent"] 427 | rec_thresh_absolute = cfg["filterPointsUSGS"]["rec_thresh_absolute"] 428 | proj_thresh_percent = cfg["filterPointsUSGS"]["proj_thresh_percent"] 429 | proj_thresh_absolute = cfg["filterPointsUSGS"]["proj_thresh_absolute"] 430 | reproj_thresh_percent = cfg["filterPointsUSGS"]["reproj_thresh_percent"] 431 | reproj_thresh_absolute = cfg["filterPointsUSGS"]["reproj_thresh_absolute"] 432 | 433 | fltr = Metashape.PointCloud.Filter() 434 | fltr.init(doc.chunk, Metashape.PointCloud.Filter.ReconstructionUncertainty) 435 | values = fltr.values.copy() 436 | values.sort() 437 | thresh = values[int(len(values) * (1 - rec_thresh_percent / 100))] 438 | if thresh < rec_thresh_absolute: 439 | thresh = ( 440 | rec_thresh_absolute # don't throw away too many points if they're all good 441 | ) 442 | fltr.removePoints(thresh) 443 | 444 | doc.chunk.optimizeCameras( 445 | adaptive_fitting=cfg["optimizeCameras"]["adaptive_fitting"] 446 | ) 447 | 448 | fltr = Metashape.PointCloud.Filter() 449 | fltr.init(doc.chunk, Metashape.PointCloud.Filter.ProjectionAccuracy) 450 | values = fltr.values.copy() 451 | values.sort() 452 | thresh = values[int(len(values) * (1 - proj_thresh_percent / 100))] 453 | if thresh < proj_thresh_absolute: 454 | thresh = ( 455 | proj_thresh_absolute # don't throw away too many points if they're all good 456 | ) 457 | fltr.removePoints(thresh) 458 | 459 | doc.chunk.optimizeCameras( 460 | adaptive_fitting=cfg["optimizeCameras"]["adaptive_fitting"] 461 | ) 462 | 463 | fltr = Metashape.PointCloud.Filter() 464 | fltr.init(doc.chunk, Metashape.PointCloud.Filter.ReprojectionError) 465 | values = fltr.values.copy() 466 | values.sort() 467 | thresh = values[int(len(values) * (1 - reproj_thresh_percent / 100))] 468 | if thresh < reproj_thresh_absolute: 469 | thresh = reproj_thresh_absolute # don't throw away too many points if they're all good 470 | fltr.removePoints(thresh) 471 | 472 | doc.chunk.optimizeCameras( 473 | adaptive_fitting=cfg["optimizeCameras"]["adaptive_fitting"] 474 | ) 475 | 476 | doc.save() 477 | 478 | 479 | def filter_points_usgs_part2(doc, cfg): 480 | 481 | doc.chunk.optimizeCameras( 482 | adaptive_fitting=cfg["optimizeCameras"]["adaptive_fitting"] 483 | ) 484 | 485 | reproj_thresh_percent = cfg["filterPointsUSGS"]["reproj_thresh_percent"] 486 | reproj_thresh_absolute = cfg["filterPointsUSGS"]["reproj_thresh_absolute"] 487 | 488 | fltr = Metashape.PointCloud.Filter() 489 | fltr.init(doc.chunk, Metashape.PointCloud.Filter.ReprojectionError) 490 | values = fltr.values.copy() 491 | values.sort() 492 | thresh = values[int(len(values) * (1 - reproj_thresh_percent / 100))] 493 | if thresh < reproj_thresh_absolute: 494 | thresh = reproj_thresh_absolute # don't throw away too many points if they're all good 495 | fltr.removePoints(thresh) 496 | 497 | doc.chunk.optimizeCameras( 498 | adaptive_fitting=cfg["optimizeCameras"]["adaptive_fitting"] 499 | ) 500 | 501 | doc.save() 502 | 503 | 504 | def classify_ground_points(doc, log_file, run_id, cfg): 505 | 506 | # get a beginning time stamp for the next step 507 | timer_a = time.time() 508 | 509 | doc.chunk.dense_cloud.classifyGroundPoints( 510 | max_angle=cfg["classifyGroundPoints"]["max_angle"], 511 | max_distance=cfg["classifyGroundPoints"]["max_distance"], 512 | cell_size=cfg["classifyGroundPoints"]["cell_size"], 513 | ) 514 | doc.save() 515 | 516 | # get an ending time stamp for the previous step 517 | timer_b = time.time() 518 | 519 | # calculate difference between end and start time to 1 decimal place 520 | time_tot = diff_time(timer_b, timer_a) 521 | 522 | # record results to file 523 | with open(log_file, "a") as file: 524 | file.write(sep.join(["Classify Ground Points", time_tot]) + "\n") 525 | 526 | 527 | def build_dense_cloud(doc, log_file, run_id, cfg): 528 | """ 529 | Build depth maps and dense cloud 530 | """ 531 | 532 | ### Build depth maps 533 | 534 | # get a beginning time stamp for the next step 535 | timer2a = time.time() 536 | 537 | # build depth maps only instead of also building the dense cloud ##?? what does 538 | doc.chunk.buildDepthMaps( 539 | downscale=cfg["buildDenseCloud"]["downscale"], 540 | filter_mode=cfg["buildDenseCloud"]["filter_mode"], 541 | reuse_depth=cfg["buildDenseCloud"]["reuse_depth"], 542 | max_neighbors=cfg["buildDenseCloud"]["max_neighbors"], 543 | subdivide_task=cfg["subdivide_task"], 544 | ) 545 | doc.save() 546 | 547 | # get an ending time stamp for the previous step 548 | timer2b = time.time() 549 | 550 | # calculate difference between end and start time to 1 decimal place 551 | time2 = diff_time(timer2b, timer2a) 552 | 553 | # record results to file 554 | with open(log_file, "a") as file: 555 | file.write(sep.join(["Build Depth Maps", time2]) + "\n") 556 | 557 | ### Build dense cloud 558 | 559 | # get a beginning time stamp for the next step 560 | timer3a = time.time() 561 | 562 | # build dense cloud 563 | doc.chunk.buildDenseCloud( 564 | max_neighbors=cfg["buildDenseCloud"]["max_neighbors"], 565 | keep_depth=cfg["buildDenseCloud"]["keep_depth"], 566 | subdivide_task=cfg["subdivide_task"], 567 | point_colors=True, 568 | ) 569 | doc.save() 570 | 571 | # classify ground points if specified 572 | if cfg["buildDenseCloud"]["classify_ground_points"]: 573 | classify_ground_points(doc, log_file, run_id, cfg) 574 | 575 | ### Export points 576 | 577 | if cfg["buildDenseCloud"]["export"]: 578 | 579 | output_file = os.path.join(cfg["output_path"], run_id + "_points.las") 580 | 581 | if cfg["buildDenseCloud"]["classes"] == "ALL": 582 | # call without classes argument (Metashape then defaults to all classes) 583 | doc.chunk.exportPoints( 584 | path=output_file, 585 | source_data=Metashape.DenseCloudData, 586 | format=Metashape.PointsFormatLAS, 587 | crs=Metashape.CoordinateSystem(cfg["project_crs"]), 588 | subdivide_task=cfg["subdivide_task"], 589 | ) 590 | else: 591 | # call with classes argument 592 | doc.chunk.exportPoints( 593 | path=output_file, 594 | source_data=Metashape.DenseCloudData, 595 | format=Metashape.PointsFormatLAS, 596 | crs=Metashape.CoordinateSystem(cfg["project_crs"]), 597 | clases=cfg["buildDenseCloud"]["classes"], 598 | subdivide_task=cfg["subdivide_task"], 599 | ) 600 | 601 | # get an ending time stamp for the previous step 602 | timer3b = time.time() 603 | 604 | # calculate difference between end and start time to 1 decimal place 605 | time3 = diff_time(timer3b, timer3a) 606 | 607 | # record results to file 608 | with open(log_file, "a") as file: 609 | file.write(sep.join(["Build Dense Cloud", time3]) + "\n") 610 | 611 | return True 612 | 613 | 614 | def build_dem(doc, log_file, run_id, cfg): 615 | """ 616 | Build end export DEM 617 | """ 618 | 619 | # classify ground points if specified 620 | if cfg["buildDem"]["classify_ground_points"]: 621 | classify_ground_points(doc, log_file, run_id, cfg) 622 | 623 | # get a beginning time stamp for the next step 624 | timer5a = time.time() 625 | 626 | # prepping params for buildDem 627 | projection = Metashape.OrthoProjection() 628 | projection.crs = Metashape.CoordinateSystem(cfg["project_crs"]) 629 | 630 | # prepping params for export 631 | compression = Metashape.ImageCompression() 632 | compression.tiff_big = cfg["buildDem"]["tiff_big"] 633 | compression.tiff_tiled = cfg["buildDem"]["tiff_tiled"] 634 | compression.tiff_overviews = cfg["buildDem"]["tiff_overviews"] 635 | 636 | if (cfg["buildDem"]["type"] == "DSM") | (cfg["buildDem"]["type"] == "both"): 637 | # call without classes argument (Metashape then defaults to all classes) 638 | doc.chunk.buildDem( 639 | source_data=Metashape.DenseCloudData, 640 | subdivide_task=cfg["subdivide_task"], 641 | projection=projection, 642 | ) 643 | output_file = os.path.join(cfg["output_path"], run_id + "_dsm.tif") 644 | if cfg["buildDem"]["export"]: 645 | doc.chunk.exportRaster( 646 | path=output_file, 647 | projection=projection, 648 | nodata_value=cfg["buildDem"]["nodata"], 649 | source_data=Metashape.ElevationData, 650 | image_compression=compression, 651 | ) 652 | if (cfg["buildDem"]["type"] == "DTM") | (cfg["buildDem"]["type"] == "both"): 653 | # call with classes argument 654 | doc.chunk.buildDem( 655 | source_data=Metashape.DenseCloudData, 656 | classes=Metashape.PointClass.Ground, 657 | subdivide_task=cfg["subdivide_task"], 658 | projection=projection, 659 | ) 660 | output_file = os.path.join(cfg["output_path"], run_id + "_dtm.tif") 661 | if cfg["buildDem"]["export"]: 662 | doc.chunk.exportRaster( 663 | path=output_file, 664 | projection=projection, 665 | nodata_value=cfg["buildDem"]["nodata"], 666 | source_data=Metashape.ElevationData, 667 | image_compression=compression, 668 | ) 669 | if ( 670 | (cfg["buildDem"]["type"] != "DTM") 671 | & (cfg["buildDem"]["type"] == "both") 672 | & (cfg["buildDem"]["type"] == "DSM") 673 | ): 674 | raise ValueError("DEM type must be either 'DSM' or 'DTM' or 'both'") 675 | 676 | doc.save() 677 | 678 | # get an ending time stamp for the previous step 679 | timer5b = time.time() 680 | 681 | # calculate difference between end and start time to 1 decimal place 682 | time5 = diff_time(timer5b, timer5a) 683 | 684 | # record results to file 685 | with open(log_file, "a") as file: 686 | file.write(sep.join(["Build DEM", time5]) + "\n") 687 | 688 | return True 689 | 690 | 691 | def build_export_orthomosaic(doc, log_file, run_id, cfg, file_ending): 692 | """ 693 | Helper function called by build_orthomosaics. build_export_orthomosaic builds and exports an ortho based on the current elevation data. 694 | build_orthomosaics sets the current elevation data and calls build_export_orthomosaic (one or more times depending on how many orthomosaics requested) 695 | """ 696 | 697 | # get a beginning time stamp for the next step 698 | timer6a = time.time() 699 | 700 | # prepping params for buildDem 701 | projection = Metashape.OrthoProjection() 702 | projection.crs = Metashape.CoordinateSystem(cfg["project_crs"]) 703 | 704 | doc.chunk.buildOrthomosaic( 705 | surface_data=Metashape.ElevationData, 706 | blending_mode=cfg["buildOrthomosaic"]["blending"], 707 | fill_holes=cfg["buildOrthomosaic"]["fill_holes"], 708 | refine_seamlines=cfg["buildOrthomosaic"]["refine_seamlines"], 709 | subdivide_task=cfg["subdivide_task"], 710 | projection=projection, 711 | ) 712 | 713 | doc.save() 714 | 715 | ## Export orthomosaic 716 | if cfg["buildOrthomosaic"]["export"]: 717 | output_file = os.path.join( 718 | cfg["output_path"], run_id + "_ortho_" + file_ending + ".tif" 719 | ) 720 | 721 | compression = Metashape.ImageCompression() 722 | compression.tiff_big = cfg["buildOrthomosaic"]["tiff_big"] 723 | compression.tiff_tiled = cfg["buildOrthomosaic"]["tiff_tiled"] 724 | compression.tiff_overviews = cfg["buildOrthomosaic"]["tiff_overviews"] 725 | 726 | projection = Metashape.OrthoProjection() 727 | projection.crs = Metashape.CoordinateSystem(cfg["project_crs"]) 728 | 729 | doc.chunk.exportRaster( 730 | path=output_file, 731 | projection=projection, 732 | nodata_value=cfg["buildOrthomosaic"]["nodata"], 733 | source_data=Metashape.OrthomosaicData, 734 | image_compression=compression, 735 | ) 736 | 737 | # get an ending time stamp for the previous step 738 | timer6b = time.time() 739 | 740 | # calculate difference between end and start time to 1 decimal place 741 | time6 = diff_time(timer6b, timer6a) 742 | 743 | # record results to file 744 | with open(log_file, "a") as file: 745 | file.write(sep.join(["Build Orthomosaic", time6]) + "\n") 746 | 747 | return True 748 | 749 | 750 | def build_orthomosaics(doc, log_file, run_id, cfg): 751 | """ 752 | Build orthomosaic. This function just calculates the needed elevation data(s) and then calls build_export_orthomosaic to do the actual building and exporting. It does this multiple times if orthos based on multiple surfaces were requsted 753 | """ 754 | 755 | # prep projection for export step below (in case export is enabled) 756 | projection = Metashape.OrthoProjection() 757 | projection.crs = Metashape.CoordinateSystem(cfg["project_crs"]) 758 | 759 | # get a beginning time stamp for the next step 760 | timer6a = time.time() 761 | 762 | # what should the orthomosaic filename end in? e.g., DSM, DTM, USGS to indicate the surface it was built on 763 | file_ending = cfg["buildOrthomosaic"]["surface"] 764 | 765 | # Import USGS DEM as surface for orthomosaic if specified 766 | if cfg["buildOrthomosaic"]["surface"] == "USGS": 767 | path = os.path.join(cfg["photo_path"], cfg["buildOrthomosaic"]["usgs_dem_path"]) 768 | crs = Metashape.CoordinateSystem(cfg["buildOrthomosaic"]["usgs_dem_crs"]) 769 | doc.chunk.importRaster(path=path, crs=crs, raster_type=Metashape.ElevationData) 770 | build_export_orthomosaic(doc, log_file, run_id, cfg, file_ending="USGS") 771 | # Otherwise use Metashape point cloud to build elevation model 772 | # DTM: use ground points only 773 | if (cfg["buildOrthomosaic"]["surface"] == "DTM") | ( 774 | cfg["buildOrthomosaic"]["surface"] == "DTMandDSM" 775 | ): 776 | doc.chunk.buildDem( 777 | source_data=Metashape.DenseCloudData, 778 | classes=Metashape.PointClass.Ground, 779 | subdivide_task=cfg["subdivide_task"], 780 | projection=projection, 781 | ) 782 | build_export_orthomosaic(doc, log_file, run_id, cfg, file_ending="dtm") 783 | # DSM: use all point classes 784 | if (cfg["buildOrthomosaic"]["surface"] == "DSM") | ( 785 | cfg["buildOrthomosaic"]["surface"] == "DTMandDSM" 786 | ): 787 | doc.chunk.buildDem( 788 | source_data=Metashape.DenseCloudData, 789 | subdivide_task=cfg["subdivide_task"], 790 | projection=projection, 791 | ) 792 | build_export_orthomosaic(doc, log_file, run_id, cfg, file_ending="dsm") 793 | 794 | return True 795 | 796 | 797 | def export_report(doc, run_id, cfg): 798 | """ 799 | Export report 800 | """ 801 | 802 | output_file = os.path.join(cfg["output_path"], run_id + "_report.pdf") 803 | 804 | doc.chunk.exportReport(path=output_file) 805 | 806 | return True 807 | 808 | 809 | def finish_run(log_file, config_file): 810 | """ 811 | Finish run (i.e., write completed time to log) 812 | """ 813 | 814 | # finish local results log and close it for the last time 815 | with open(log_file, "a") as file: 816 | file.write(sep.join(["Run Completed", stamp_time()]) + "\n") 817 | 818 | # open run configuration again. We can't just use the existing cfg file because its objects had already been converted to Metashape objects (they don't write well) 819 | with open(config_file) as file: 820 | config_full = yaml.safe_load(file) 821 | 822 | # write the run configuration to the log file 823 | with open(log_file, "a") as file: 824 | file.write("\n\n### CONFIGURATION ###\n") 825 | documents = yaml.dump(config_full, file, default_flow_style=False) 826 | file.write("### END CONFIGURATION ###\n") 827 | 828 | return True 829 | -------------------------------------------------------------------------------- /prior-versions/metashape_v1.6-1.8/python/read_yaml.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on Mon Oct 21 13:45:15 2019 3 | 4 | @author: Alex Mandel 5 | """ 6 | 7 | import Metashape 8 | import yaml 9 | 10 | 11 | def convert_objects(a_dict): 12 | """ 13 | Based on 14 | https://stackoverflow.com/a/25896596/237354 15 | """ 16 | for k, v in a_dict.items(): 17 | if not isinstance(v, dict): 18 | if isinstance(v, str): 19 | # TODO look for Metashape. 20 | if ( 21 | v 22 | and "Metashape" in v 23 | and not ("path" in k) 24 | and not ("project" in k) 25 | ): # allow "path" and "project" keys from YAML to include "Metashape" (e.g., Metashape in the filename) 26 | a_dict[k] = eval(v) 27 | elif isinstance(v, list): 28 | # TODO skip if no item have metashape 29 | a_dict[k] = [eval(item) for item in v if ("Metashape" in item)] 30 | else: 31 | convert_objects(v) 32 | 33 | 34 | def read_yaml(yml_path): 35 | with open(yml_path, "r") as ymlfile: 36 | cfg = yaml.load(ymlfile, Loader=yaml.SafeLoader) 37 | 38 | # TODO: wrap in a Try to catch errors 39 | convert_objects(cfg) 40 | 41 | return cfg 42 | 43 | 44 | if __name__ == "__main__": 45 | 46 | yml_path = "config/example.yml" 47 | cfg = read_yaml(yml_path) 48 | # with open("config/example.yml",'r') as ymlfile: 49 | # cfg = yaml.load(ymlfile) 50 | 51 | # catch = convert_objects(cfg) 52 | 53 | # Get a String value 54 | Photo_path = cfg["Photo_path"] 55 | 56 | # Get a True/False 57 | GPU_use = cfg["GPU"]["GPU_use"] 58 | 59 | # Get a Num 60 | GPU_num = cfg["GPU"]["GPU_num"] 61 | 62 | # Convert a to a Metashape Object 63 | accuracy = eval(cfg["matchPhotos"]["accuracy"]) 64 | -------------------------------------------------------------------------------- /python/metashape_workflow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # File for running a metashape workflow 3 | 4 | import argparse 5 | import contextlib 6 | import sys 7 | from pathlib import Path 8 | 9 | # ---- If this is a first run from the standalone python module, need to copy the license file from the full metashape install: from python import metashape_license_setup 10 | 11 | ## Define where to get the config file (only used if running interactively) 12 | # manual_config_file = "config/config-base.yml" 13 | manual_config_file = Path( 14 | Path(__file__).parent, "..", "config", "config-base.yml" 15 | ).resolve() 16 | # ---- If not running interactively, the config file should be supplied as the command-line argument after the python script, e.g.: python metashape_workflow.py config.yml 17 | 18 | 19 | # Load custom modules and config file: slightly different depending whether running interactively or via command line 20 | try: # running interactively (in linux) or command line (windows) 21 | from python.metashape_workflow_functions import MetashapeWorkflow 22 | except: # running from command line (in linux) or interactively (windows) 23 | from metashape_workflow_functions import MetashapeWorkflow 24 | 25 | 26 | def parse_args(): 27 | parser = argparse.ArgumentParser( 28 | description="The first required argument is the path to the config file. " 29 | + "All other arguments are optional overrides to the corresponding entry in that config" 30 | ) 31 | parser.add_argument( 32 | "--config_file", 33 | default=manual_config_file, 34 | help="A path to a yaml config file.", 35 | ) 36 | parser.add_argument( 37 | "--photo-path", 38 | nargs="+", 39 | help="One or more absolute paths to load photos from, separated by spaces.", 40 | ) 41 | parser.add_argument( 42 | "--photo-path-secondary", 43 | help="A path to a folder of images to add after alignment. " 44 | + "For more information, see the description in the example config file.", 45 | ) 46 | parser.add_argument( 47 | "--project-path", 48 | help="Path to save Metashape project file (.psx). Will be created if does not exist", 49 | ) 50 | parser.add_argument( 51 | "--output-path", 52 | help="Path for exports (e.g., points, DSM, orthomosaic) and processing log. " 53 | + "Will be created if does not exist.", 54 | ) 55 | parser.add_argument( 56 | "--run-name", 57 | help="The identifier for the run. Will be used in naming output files.", 58 | ) 59 | parser.add_argument( 60 | "--project-crs", 61 | help="CRS EPSG code that project outputs should be in " 62 | + "(projection should be in meter units and intended for the project area). " 63 | + "It should be specified in the following format: 'EPSG::'.", 64 | ) 65 | 66 | args = parser.parse_args() 67 | return args 68 | 69 | 70 | if __name__ == "__main__": 71 | args = parse_args() 72 | 73 | # Get the non-None overrides provided on the command line 74 | override_dict = {k: v for k, v in args.__dict__.items() if v is not None} 75 | 76 | # Initialize the workflow instance with the configuration file and the dictionary representation of 77 | # CLI overrides 78 | meta = MetashapeWorkflow(config_file=args.config_file, override_dict=override_dict) 79 | 80 | ### Run the Metashape workflow 81 | # The argo workflow requires that all stdout is json formatted. Since this isn't the case for the 82 | # metashape logs, we redirect to standard error. 83 | with contextlib.redirect_stdout(sys.stderr): 84 | # Actually run the processing step 85 | try: 86 | meta.run() 87 | except Exception as e: 88 | # TODO make this error message more descriptive 89 | print( 90 | "Metashape errored while processing, the completed paths will still be reported. " 91 | + "The error was: \n" 92 | + e.__str__() 93 | ) 94 | 95 | # Log where the data files were written as json dict 96 | print(meta.get_written_paths(as_json=True)) 97 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "editor.rulers": [ 4 | 5 | 100 6 | ], 7 | "rewrap.autoWrap.enabled": false 8 | 9 | } --------------------------------------------------------------------------------