Porównaj commity

..

No commits in common. "master" and "v3.5.3" have entirely different histories.

37 zmienionych plików z 96 dodań i 342 usunięć

Wyświetl plik

@ -13,7 +13,6 @@ jobs:
with:
ghToken: ${{ secrets.GITHUB_TOKEN }}
openAI: ${{ secrets.OPENAI_TOKEN }}
model: gpt-4o-2024-08-06
filter: |
- "#"
variables: |

Wyświetl plik

@ -49,7 +49,7 @@ jobs:
run: |
python configure.py dist --code-sign-cert-path $env:CODE_SIGN_CERT_PATH
- name: Upload Setup File
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v2
with:
name: Setup
path: dist\*.exe

Wyświetl plik

@ -81,7 +81,7 @@ jobs:
run: |
python configure.py dist
- name: Upload Setup File
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v2
with:
name: Setup
path: dist\*.exe

Wyświetl plik

@ -15,13 +15,7 @@ If you would rather not type commands in a shell and are looking for a friendly
## Quickstart
The easiest way to run ODM is via docker. To install docker, see [docs.docker.com](https://docs.docker.com). Once you have docker installed and [working](https://docs.docker.com/get-started/#test-docker-installation), you can get ODM by running from a Command Prompt / Terminal:
```bash
docker pull opendronemap/odm
```
Run ODM by placing some images (JPEGs, TIFFs or DNGs) in a folder named “images” (for example `C:\Users\youruser\datasets\project\images` or `/home/youruser/datasets/project/images`) and simply run from a Command Prompt / Terminal:
The easiest way to run ODM on is via docker. To install docker, see [docs.docker.com](https://docs.docker.com). Once you have docker installed and [working](https://docs.docker.com/get-started/#test-docker-installation), you can run ODM by placing some images (JPEGs or TIFFs) in a folder named “images” (for example `C:\Users\youruser\datasets\project\images` or `/home/youruser/datasets/project/images`) and simply run from a Command Prompt / Terminal:
```bash
# Windows

Wyświetl plik

@ -244,7 +244,7 @@ externalproject_add(dem2points
externalproject_add(odm_orthophoto
DEPENDS opencv
GIT_REPOSITORY https://github.com/OpenDroneMap/odm_orthophoto.git
GIT_TAG 355
GIT_TAG 353
PREFIX ${SB_BINARY_DIR}/odm_orthophoto
SOURCE_DIR ${SB_SOURCE_DIR}/odm_orthophoto
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR}

Wyświetl plik

@ -1,7 +1,7 @@
set(_proj_name obj2tiles)
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
set(OBJ2TILES_VERSION v1.0.13)
set(OBJ2TILES_VERSION v1.0.12)
set(OBJ2TILES_EXT "")
set(OBJ2TILES_ARCH "Linux64")
@ -9,7 +9,7 @@ if (WIN32)
set(OBJ2TILES_ARCH "Win64")
set(OBJ2TILES_EXT ".exe")
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
set(OBJ2TILES_ARCH "LinuxArm64")
set(OBJ2TILES_ARCH "LinuxArm")
elseif(APPLE)
set(OBJ2TILES_ARCH "Osx64")
endif()

Wyświetl plik

@ -53,7 +53,7 @@ ExternalProject_Add(${_proj_name}
#--Download step--------------
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
GIT_REPOSITORY https://github.com/OpenDroneMap/openMVS
GIT_TAG 355
GIT_TAG 320
#--Update/Patch step----------
UPDATE_COMMAND ""
#--Configure step-------------

Wyświetl plik

@ -25,7 +25,7 @@ ExternalProject_Add(${_proj_name}
#--Download step--------------
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
GIT_REPOSITORY https://github.com/OpenDroneMap/OpenSfM/
GIT_TAG 355
GIT_TAG 352
#--Update/Patch step----------
UPDATE_COMMAND git submodule update --init --recursive
#--Configure step-------------

Wyświetl plik

@ -1 +1 @@
3.5.5
3.5.3

Wyświetl plik

@ -1,4 +1,5 @@
import bpy
import materials_utils
def loadMesh(file):

Wyświetl plik

@ -1,13 +0,0 @@
# DEM Blending
Blend sets of DEMs by calculating euclidean distance to null values and weighting the combination of elevation models. Based on the split-merge tool within ODM.
Requirements:
* Directory full of images to blend together
* NoData should be coded as a value of -9999
## Usage
```BASH
docker run -ti --rm -v /home/youruser/folder_with_dems:/input --entrypoint /code/contrib/dem-blend/dem-blend.py opendronemap/odm /input
```

Wyświetl plik

@ -1,30 +0,0 @@
#!/usr/bin/env python3
# Authors: Piero Toffanin, Stephen Mather
# License: AGPLv3
import os
import glob
import sys
sys.path.insert(0, os.path.join("..", "..", os.path.dirname(__file__)))
import argparse
from opendm.dem import merge
parser = argparse.ArgumentParser(description='Merge and blend DEMs using OpenDroneMap\'s approach.')
parser.add_argument('input_dems',
type=str,
help='Path to input dems (.tif)')
args = parser.parse_args()
if not os.path.exists(args.input_dems):
print("%s does not exist" % args.input_dems)
exit(1)
output_dem = os.path.join(args.input_dems, 'merged_blended_dem.tif')
input_dem_path = os.path.join(args.input_dems, '*.tif')
input_dems = glob.glob(input_dem_path)
merge.euclidean_merge_dems(input_dems
,output_dem=output_dem
)

Wyświetl plik

@ -4,7 +4,7 @@ import os
import PIL
from PIL import Image
from PIL import Image, ExifTags
import shutil

Wyświetl plik

@ -12,6 +12,7 @@ import numpy as np
import numpy.ma as ma
import multiprocessing
import argparse
import functools
from skimage.draw import line
from opensfm import dataset

Wyświetl plik

@ -38,7 +38,6 @@ COPY --from=builder /code /code
# Copy the Python libraries installed via pip from the builder
COPY --from=builder /usr/local /usr/local
#COPY --from=builder /usr/lib/x86_64-linux-gnu/libavcodec.so.58 /usr/lib/x86_64-linux-gnu/libavcodec.so.58
RUN dpkg --remove cuda-compat-11-2
RUN apt-get update -y \
&& apt-get install -y ffmpeg libtbb2
# Install shared libraries that we depend on via APT, but *not*

Wyświetl plik

@ -4,25 +4,6 @@ from opendm import log
import zipfile
import time
import sys
import rawpy
import cv2
def read_image(img_path):
if img_path[-4:].lower() in [".dng", ".raw", ".nef"]:
try:
with rawpy.imread(img_path) as r:
img = r.postprocess(output_bps=8, use_camera_wb=True, use_auto_wb=False)
except:
return None
else:
img = cv2.imread(img_path, cv2.IMREAD_COLOR)
if img is None:
return None
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img
def get_model(namespace, url, version, name = "model.onnx"):
version = version.replace(".", "_")

Wyświetl plik

@ -5,7 +5,6 @@ import cv2
import os
import onnxruntime as ort
from opendm import log
from opendm.ai import read_image
from threading import Lock
mutex = Lock()
@ -74,7 +73,11 @@ class BgFilter():
return output
def run_img(self, img_path, dest):
img = read_image(img_path)
img = cv2.imread(img_path, cv2.IMREAD_COLOR)
if img is None:
return None
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = self.get_mask(img)
img_name = os.path.basename(img_path)

Wyświetl plik

@ -127,7 +127,7 @@ def url_string(string):
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
if re.match(regex, string) is None:
raise argparse.ArgumentTypeError("%s is not a valid URL. The URL must be in the format: http(s)://host[:port]/[?token=]" % string)
return string
@ -164,7 +164,7 @@ def config(argv=None, parser=None):
parser = SettingsParser(description='ODM is a command line toolkit to generate maps, point clouds, 3D models and DEMs from drone, balloon or kite images.',
usage='%s [options] <dataset name>' % usage_bin,
yaml_file=open(context.settings_path))
parser.add_argument('--project-path',
metavar='<path>',
action=StoreValue,
@ -213,7 +213,7 @@ def config(argv=None, parser=None):
'More features can be useful for finding more matches between images, '
'potentially allowing the reconstruction of areas with little overlap or insufficient features. '
'More features also slow down processing. Default: %(default)s'))
parser.add_argument('--feature-type',
metavar='<string>',
action=StoreValue,
@ -222,7 +222,7 @@ def config(argv=None, parser=None):
help=('Choose the algorithm for extracting keypoints and computing descriptors. '
'Can be one of: %(choices)s. Default: '
'%(default)s'))
parser.add_argument('--feature-quality',
metavar='<string>',
action=StoreValue,
@ -231,7 +231,7 @@ def config(argv=None, parser=None):
help=('Set feature extraction quality. Higher quality generates better features, but requires more memory and takes longer. '
'Can be one of: %(choices)s. Default: '
'%(default)s'))
parser.add_argument('--matcher-type',
metavar='<string>',
action=StoreValue,
@ -247,7 +247,7 @@ def config(argv=None, parser=None):
default=0,
type=int,
help='Perform image matching with the nearest images based on GPS exif data. Set to 0 to match by triangulation. Default: %(default)s')
parser.add_argument('--matcher-order',
metavar='<positive integer>',
action=StoreValue,
@ -331,7 +331,7 @@ def config(argv=None, parser=None):
nargs=0,
default=False,
help='Automatically compute image masks using AI to remove the sky. Experimental. Default: %(default)s')
parser.add_argument('--bg-removal',
action=StoreTrue,
nargs=0,
@ -349,19 +349,19 @@ def config(argv=None, parser=None):
nargs=0,
default=False,
help='Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs. Default: %(default)s')
parser.add_argument('--skip-report',
action=StoreTrue,
nargs=0,
default=False,
help='Skip generation of PDF report. This can save time if you don\'t need a report. Default: %(default)s')
parser.add_argument('--skip-orthophoto',
action=StoreTrue,
nargs=0,
default=False,
help='Skip generation of the orthophoto. This can save time if you only need 3D results or DEMs. Default: %(default)s')
parser.add_argument('--ignore-gsd',
action=StoreTrue,
nargs=0,
@ -371,13 +371,13 @@ def config(argv=None, parser=None):
'Ordinarily, GSD estimates are used to cap the maximum resolution of image outputs and resizes images when necessary, resulting in faster processing and lower memory usage. '
'Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality. '
'Never set --ignore-gsd to true unless you are positive you need it, and even then: do not use it. Default: %(default)s')
parser.add_argument('--no-gpu',
action=StoreTrue,
nargs=0,
default=False,
help='Do not use GPU acceleration, even if it\'s available. Default: %(default)s')
parser.add_argument('--mesh-size',
metavar='<positive integer>',
action=StoreValue,
@ -462,7 +462,7 @@ def config(argv=None, parser=None):
nargs=0,
default=False,
help='Export the georeferenced point cloud in CSV format. Default: %(default)s')
parser.add_argument('--pc-las',
action=StoreTrue,
nargs=0,
@ -488,7 +488,7 @@ def config(argv=None, parser=None):
default=5,
help='Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering. '
'Default: %(default)s')
parser.add_argument('--pc-sample',
metavar='<positive float>',
action=StoreValue,
@ -519,7 +519,7 @@ def config(argv=None, parser=None):
default=0.15,
help='Simple Morphological Filter slope parameter (rise over run). '
'Default: %(default)s')
parser.add_argument('--smrf-threshold',
metavar='<positive float>',
action=StoreValue,
@ -527,7 +527,7 @@ def config(argv=None, parser=None):
default=0.5,
help='Simple Morphological Filter elevation threshold parameter (meters). '
'Default: %(default)s')
parser.add_argument('--smrf-window',
metavar='<positive float>',
action=StoreValue,
@ -586,7 +586,7 @@ def config(argv=None, parser=None):
'EPSG:<code> or <+proj definition>\n'
'image_name geo_x geo_y geo_z [yaw (degrees)] [pitch (degrees)] [roll (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]\n'
'Default: %(default)s'))
parser.add_argument('--align',
metavar='<path string>',
action=StoreValue,
@ -642,7 +642,7 @@ def config(argv=None, parser=None):
type=int,
help='Decimate the points before generating the DEM. 1 is no decimation (full quality). '
'100 decimates ~99%% of the points. Useful for speeding up generation of DEM results in very large datasets. Default: %(default)s')
parser.add_argument('--dem-euclidean-map',
action=StoreTrue,
nargs=0,
@ -675,13 +675,13 @@ def config(argv=None, parser=None):
default=False,
help='Set this parameter if you want to generate a PNG rendering of the orthophoto. '
'Default: %(default)s')
parser.add_argument('--orthophoto-kmz',
action=StoreTrue,
nargs=0,
default=False,
help='Set this parameter if you want to generate a Google Earth (KMZ) rendering of the orthophoto. '
'Default: %(default)s')
'Default: %(default)s')
parser.add_argument('--orthophoto-compression',
metavar='<string>',
@ -690,7 +690,7 @@ def config(argv=None, parser=None):
choices=['JPEG', 'LZW', 'PACKBITS', 'DEFLATE', 'LZMA', 'NONE'],
default='DEFLATE',
help='Set the compression to use for orthophotos. Can be one of: %(choices)s. Default: %(default)s')
parser.add_argument('--orthophoto-cutline',
action=StoreTrue,
nargs=0,
@ -729,7 +729,7 @@ def config(argv=None, parser=None):
action=StoreValue,
metavar='<positive integer>',
default=0,
help='Override the rolling shutter readout time for your camera sensor (in milliseconds), instead of using the rolling shutter readout database. '
help='Override the rolling shutter readout time for your camera sensor (in milliseconds), instead of using the rolling shutter readout database. '
'Note that not all cameras are present in the database. Set to 0 to use the database value. '
'Default: %(default)s')
@ -768,7 +768,7 @@ def config(argv=None, parser=None):
default=4000,
metavar='<positive integer>',
help='The maximum output resolution of extracted video frames in pixels. Default: %(default)s')
parser.add_argument('--split',
type=int,
action=StoreValue,
@ -785,12 +785,11 @@ def config(argv=None, parser=None):
action=StoreValue,
metavar='<positive integer>',
default=150,
help='Radius of the overlap between submodels in meters. '
help='Radius of the overlap between submodels. '
'After grouping images into clusters, images '
'that are closer than this radius to a cluster '
'are added to the cluster. This is done to ensure '
'that neighboring submodels overlap. All images' \
'need GPS information. Default: %(default)s')
'that neighboring submodels overlap. Default: %(default)s')
parser.add_argument('--split-image-groups',
metavar='<path string>',
@ -834,7 +833,7 @@ def config(argv=None, parser=None):
help=('Use images\' GPS exif data for reconstruction, even if there are GCPs present.'
'This flag is useful if you have high precision GPS measurements. '
'If there are no GCPs, this flag does nothing. Default: %(default)s'))
parser.add_argument('--gps-accuracy',
type=float,
action=StoreValue,
@ -869,7 +868,7 @@ def config(argv=None, parser=None):
default="auto",
type=str,
help=('When processing multispectral datasets, you can specify the name of the primary band that will be used for reconstruction. '
'It\'s recommended to choose a band which has sharp details and is in focus. '
'It\'s recommended to choose a band which has sharp details and is in focus. '
'Default: %(default)s'))
parser.add_argument('--skip-band-alignment',
@ -924,5 +923,5 @@ def config(argv=None, parser=None):
except exceptions.NodeConnectionError as e:
log.ODM_ERROR("Cluster node seems to be offline: %s" % str(e))
sys.exit(1)
return args

Wyświetl plik

@ -40,7 +40,7 @@ odm_orthophoto_path = os.path.join(superbuild_bin_path, "odm_orthophoto")
settings_path = os.path.join(root_path, 'settings.yaml')
# Define supported image extensions
supported_extensions = {'.jpg','.jpeg','.png', '.tif', '.tiff', '.bmp', '.raw', '.dng', '.nef'}
supported_extensions = {'.jpg','.jpeg','.png', '.tif', '.tiff', '.bmp'}
supported_video_extensions = {'.mp4', '.mov', '.lrv', '.ts'}
# Define the number of cores

Wyświetl plik

@ -1,5 +1,4 @@
import os
import subprocess
import sys
import rasterio
import numpy
@ -21,8 +20,6 @@ from opendm import log
from .ground_rectification.rectify import run_rectification
from . import pdal
gdal_proximity = None
try:
# GDAL >= 3.3
from osgeo_utils.gdal_proximity import main as gdal_proximity
@ -30,13 +27,8 @@ except ModuleNotFoundError:
# GDAL <= 3.2
try:
from osgeo.utils.gdal_proximity import main as gdal_proximity
except ModuleNotFoundError:
# GDAL <= 3.0
gdal_proximity_script = shutil.which("gdal_proximity.py")
if gdal_proximity_script is not None:
def gdal_proximity(args):
subprocess.run([gdal_proximity_script] + args[1:], check=True)
except:
pass
def classify(lasFile, scalar, slope, threshold, window):
start = datetime.now()
@ -271,4 +263,4 @@ def get_dem_radius_steps(stats_file, steps, resolution, multiplier = 1.0):
for _ in range(steps - 1):
radius_steps.append(radius_steps[-1] * math.sqrt(2))
return radius_steps
return radius_steps

Wyświetl plik

@ -1,6 +1,6 @@
from PIL import Image
import cv2
import rawpy
from opendm import log
Image.MAX_IMAGE_PIXELS = None
@ -9,18 +9,12 @@ def get_image_size(file_path, fallback_on_error=True):
"""
Return (width, height) for a given img file
"""
try:
if file_path[-4:].lower() in [".dng", ".raw", ".nef"]:
with rawpy.imread(file_path) as img:
s = img.sizes
width, height = s.raw_width, s.raw_height
else:
with Image.open(file_path) as img:
width, height = img.size
with Image.open(file_path) as img:
width, height = img.size
except Exception as e:
if fallback_on_error:
log.ODM_WARNING("Cannot read %s with image library, fallback to cv2: %s" % (file_path, str(e)))
log.ODM_WARNING("Cannot read %s with PIL, fallback to cv2: %s" % (file_path, str(e)))
img = cv2.imread(file_path)
width = img.shape[1]
height = img.shape[0]

Wyświetl plik

@ -6,7 +6,6 @@ import numpy as np
import pygltflib
from opendm import system
from opendm import io
from opendm import log
warnings.filterwarnings("ignore", category=rasterio.errors.NotGeoreferencedWarning)

Wyświetl plik

@ -6,7 +6,6 @@ import datetime
import dateutil.parser
import shutil
import multiprocessing
from repoze.lru import lru_cache
from opendm.arghelpers import double_quote, args_to_dict
from vmem import virtual_memory
@ -31,7 +30,6 @@ else:
lock = threading.Lock()
@lru_cache(maxsize=None)
def odm_version():
with open(os.path.join(os.path.dirname(__file__), "..", "VERSION")) as f:
return f.read().split("\n")[0].strip()

Wyświetl plik

@ -273,10 +273,7 @@ def compute_band_maps(multi_camera, primary_band):
# Quick check
if filename_without_band == p.filename:
raise Exception("Cannot match bands by filename on %s, make sure to name your files [filename]_band[.ext] uniformly." % p.filename)
if not filename_without_band in filename_map:
raise Exception("Cannot match bands by filename on %s, make sure to name your files [filename]_band[.ext] uniformly, check that your images have the appropriate CaptureUUID XMP tag and that no images are missing." % p.filename)
s2p[p.filename] = filename_map[filename_without_band]
if band['name'] != band_name:

Wyświetl plik

@ -14,7 +14,6 @@ from opendm import io
from opendm.tiles.tiler import generate_orthophoto_tiles
from opendm.cogeo import convert_to_cogeo
from osgeo import gdal
from osgeo import ogr
def get_orthophoto_vars(args):
@ -44,50 +43,33 @@ def generate_png(orthophoto_file, output_file=None, outsize=None):
output_file = base + '.png'
# See if we need to select top three bands
params = []
bandparam = ""
try:
gtif = gdal.Open(orthophoto_file)
gtif = gdal.Open(orthophoto_file)
if gtif.RasterCount > 4:
bands = []
for idx in range(1, gtif.RasterCount+1):
bands.append(gtif.GetRasterBand(idx).GetColorInterpretation())
bands = dict(zip(bands, range(1, len(bands)+1)))
if gtif.RasterCount >= 3:
try:
red = bands.get(gdal.GCI_RedBand)
green = bands.get(gdal.GCI_GreenBand)
blue = bands.get(gdal.GCI_BlueBand)
if red is None or green is None or blue is None:
params.append("-b 1 -b 2 -b 3")
else:
params.append("-b %s -b %s -b %s" % (red, green, blue))
elif gtif.RasterCount <= 2:
params.append("-b 1")
alpha = bands.get(gdal.GCI_AlphaBand)
if alpha is not None:
params.append("-b %s" % alpha)
else:
params.append("-a_nodata 0")
raise Exception("Cannot find bands")
dtype = gtif.GetRasterBand(1).DataType
if dtype != gdal.GDT_Byte:
params.append("-ot Byte")
if gtif.RasterCount >= 3:
params.append("-scale_1 -scale_2 -scale_3")
elif gtif.RasterCount <= 2:
params.append("-scale_1")
gtif = None
except Exception as e:
log.ODM_WARNING("Cannot read orthophoto information for PNG generation: %s" % str(e))
bandparam = "-b %s -b %s -b %s -a_nodata 0" % (red, green, blue)
except:
bandparam = "-b 1 -b 2 -b 3 -a_nodata 0"
gtif = None
osparam = ""
if outsize is not None:
params.append("-outsize %s 0" % outsize)
osparam = "-outsize %s 0" % outsize
system.run('gdal_translate -of png "%s" "%s" %s '
'-co WORLDFILE=YES '
'--config GDAL_CACHEMAX %s%% ' % (orthophoto_file, output_file, " ".join(params), get_max_memory()))
system.run('gdal_translate -of png "%s" "%s" %s %s '
'--config GDAL_CACHEMAX %s%% ' % (orthophoto_file, output_file, osparam, bandparam, get_max_memory()))
def generate_kmz(orthophoto_file, output_file=None, outsize=None):
if output_file is None:
@ -102,70 +84,7 @@ def generate_kmz(orthophoto_file, output_file=None, outsize=None):
system.run('gdal_translate -of KMLSUPEROVERLAY -co FORMAT=PNG "%s" "%s" %s '
'--config GDAL_CACHEMAX %s%% ' % (orthophoto_file, output_file, bandparam, get_max_memory()))
def generate_extent_polygon(orthophoto_file):
"""Function to return the orthophoto extent as a polygon into a gpkg file
Args:
orthophoto_file (str): the path to orthophoto file
"""
base, ext = os.path.splitext(orthophoto_file)
output_file = base + '_extent.dxf'
try:
gtif = gdal.Open(orthophoto_file)
srs = gtif.GetSpatialRef()
geoTransform = gtif.GetGeoTransform()
# calculate the coordinates
minx = geoTransform[0]
maxy = geoTransform[3]
maxx = minx + geoTransform[1] * gtif.RasterXSize
miny = maxy + geoTransform[5] * gtif.RasterYSize
# create polygon in wkt format
poly_wkt = "POLYGON ((%s %s, %s %s, %s %s, %s %s, %s %s))" % (minx, miny, minx, maxy, maxx, maxy, maxx, miny, minx, miny)
# create vector file
# just the DXF to support AutoCAD users
# to load the geotiff raster correctly.
driver = ogr.GetDriverByName("DXF")
ds = driver.CreateDataSource(output_file)
layer = ds.CreateLayer("extent", srs, ogr.wkbPolygon)
# create the feature and set values
featureDefn = layer.GetLayerDefn()
feature = ogr.Feature(featureDefn)
feature.SetGeometry(ogr.CreateGeometryFromWkt(poly_wkt))
# add feature to layer
layer.CreateFeature(feature)
# save and close everything
feature = None
ds = None
gtif = None
log.ODM_INFO("Wrote %s" % output_file)
except Exception as e:
log.ODM_WARNING("Cannot create extent layer for %s: %s" % (orthophoto_file, str(e)))
def generate_tfw(orthophoto_file):
base, ext = os.path.splitext(orthophoto_file)
tfw_file = base + '.tfw'
try:
with rasterio.open(orthophoto_file) as ds:
t = ds.transform
with open(tfw_file, 'w') as f:
# rasterio affine values taken by
# https://mharty3.github.io/til/GIS/raster-affine-transforms/
f.write("\n".join([str(v) for v in [t.a, t.d, t.b, t.e, t.c, t.f]]) + "\n")
log.ODM_INFO("Wrote %s" % tfw_file)
except Exception as e:
log.ODM_WARNING("Cannot create .tfw for %s: %s" % (orthophoto_file, str(e)))
def post_orthophoto_steps(args, bounds_file_path, orthophoto_file, orthophoto_tiles_dir, resolution):
if args.crop > 0 or args.boundary:
Cropper.crop(bounds_file_path, orthophoto_file, get_orthophoto_vars(args), keep_original=not args.optimize_disk_space, warp_options=['-dstalpha'])
@ -185,9 +104,6 @@ def post_orthophoto_steps(args, bounds_file_path, orthophoto_file, orthophoto_ti
if args.cog:
convert_to_cogeo(orthophoto_file, max_workers=args.max_concurrency, compression=args.orthophoto_compression)
generate_extent_polygon(orthophoto_file)
generate_tfw(orthophoto_file)
def compute_mask_raster(input_raster, vector_mask, output_raster, blend_distance=20, only_max_coords_feature=False):
if not os.path.exists(input_raster):
log.ODM_WARNING("Cannot mask raster, %s does not exist" % input_raster)

Wyświetl plik

@ -21,17 +21,6 @@ from opensfm.geo import ecef_from_lla
projections = ['perspective', 'fisheye', 'fisheye_opencv', 'brown', 'dual', 'equirectangular', 'spherical']
def find_mean_utc_time(photos):
utc_times = []
for p in photos:
if p.utc_time is not None:
utc_times.append(p.utc_time / 1000.0)
if len(utc_times) == 0:
return None
return np.mean(utc_times)
def find_largest_photo_dims(photos):
max_mp = 0
max_dims = None

Wyświetl plik

@ -327,7 +327,7 @@ def post_point_cloud_steps(args, tree, rerun=False):
tree.odm_georeferencing_model_laz,
tree.odm_georeferencing_model_las))
else:
log.ODM_WARNING("Found existing LAS file %s" % tree.odm_georeferencing_model_las)
log.ODM_WARNING("Found existing LAS file %s" % tree.odm_georeferencing_xyz_file)
# EPT point cloud output
if args.pc_ept:

Wyświetl plik

@ -148,16 +148,3 @@ def merge_geojson_shots(geojson_shots_files, output_geojson_file):
with open(output_geojson_file, "w") as f:
f.write(json.dumps(result))
def merge_cameras(cameras_json_files, output_cameras_file):
result = {}
for cameras_file in cameras_json_files:
with open(cameras_file, "r") as f:
cameras = json.loads(f.read())
for cam_id in cameras:
if not cam_id in result:
result[cam_id] = cameras[cam_id]
with open(output_cameras_file, "w") as f:
f.write(json.dumps(result))

Wyświetl plik

@ -6,7 +6,6 @@ import os
import onnxruntime as ort
from .guidedfilter import guided_filter
from opendm import log
from opendm.ai import read_image
from threading import Lock
mutex = Lock()
@ -73,7 +72,11 @@ class SkyFilter():
def run_img(self, img_path, dest):
img = read_image(img_path)
img = cv2.imread(img_path, cv2.IMREAD_COLOR)
if img is None:
return None
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = np.array(img / 255., dtype=np.float32)
mask = self.get_mask(img)

Wyświetl plik

@ -39,7 +39,7 @@ def generate_colored_hillshade(geotiff):
system.run('gdaldem color-relief "%s" "%s" "%s" -alpha -co ALPHA=YES' % (geotiff, relief_file, colored_dem))
system.run('gdaldem hillshade "%s" "%s" -z 1.0 -s 1.0 -az 315.0 -alt 45.0' % (geotiff, hillshade_dem))
system.run('"%s" "%s" "%s" "%s" "%s"' % (sys.executable, hsv_merge_script, colored_dem, hillshade_dem, colored_hillshade_dem))
system.run('%s "%s" "%s" "%s" "%s"' % (sys.executable, hsv_merge_script, colored_dem, hillshade_dem, colored_hillshade_dem))
return outputs
except Exception as e:

Wyświetl plik

@ -120,7 +120,6 @@ class SrtFileParser:
# <font size="36">SrtCnt : 1, DiffTime : 16ms
# 2023-01-06 18:56:48,380,821
# [iso : 3200] [shutter : 1/60.0] [fnum : 280] [ev : 0] [ct : 3925] [color_md : default] [focal_len : 240] [latitude: 0.000000] [longitude: 0.000000] [altitude: 0.000000] </font>
# </font>
# DJI Mavic Mini
# 1
@ -165,10 +164,9 @@ class SrtFileParser:
end = None
for line in f:
# Remove html tags, spaces
line = re.sub('<[^<]+?>', '', line).strip()
if not line:
# Check if line is empty
if not line.strip():
if start is not None:
self.data.append({
"start": start,
@ -195,6 +193,9 @@ class SrtFileParser:
continue
# Remove html tags
line = re.sub('<[^<]+?>', '', line)
# Search this "00:00:00,000 --> 00:00:00,016"
match = re.search("(\d{2}:\d{2}:\d{2},\d+) --> (\d{2}:\d{2}:\d{2},\d+)", line)
if match:
@ -224,14 +225,14 @@ class SrtFileParser:
("GPS \([\d\.\-]+,? ([\d\.\-]+),? [\d\.\-]+\)", lambda v: float(v) if v != 0 else None),
("RTK \([-+]?\d+\.\d+, (-?\d+\.\d+), -?\d+\)", lambda v: float(v) if v != 0 else None),
], line)
longitude = match_single([
("longitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
("longtitude : ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
("GPS \(([\d\.\-]+),? [\d\.\-]+,? [\d\.\-]+\)", lambda v: float(v) if v != 0 else None),
("RTK \((-?\d+\.\d+), [-+]?\d+\.\d+, -?\d+\)", lambda v: float(v) if v != 0 else None),
], line)
altitude = match_single([
("altitude: ([\d\.\-]+)", lambda v: float(v) if v != 0 else None),
("GPS \([\d\.\-]+,? [\d\.\-]+,? ([\d\.\-]+)\)", lambda v: float(v) if v != 0 else None),

Wyświetl plik

@ -23,7 +23,6 @@ rasterio==1.2.3 ; sys_platform == 'linux'
rasterio==1.3.6 ; sys_platform == 'darwin'
https://github.com/OpenDroneMap/windows-deps/raw/main/rasterio-1.2.3-cp38-cp38-win_amd64.whl ; sys_platform == 'win32'
https://github.com/OpenDroneMap/windows-deps/raw/main/GDAL-3.2.3-cp38-cp38-win_amd64.whl ; sys_platform == 'win32'
odmrawpy==0.24.1
repoze.lru==0.7
scikit-learn==1.1.1
Pywavelets==1.3.0

Wyświetl plik

@ -187,9 +187,6 @@ class ODMLoadDatasetStage(types.ODM_Stage):
p.compute_opk()
updated += 1
log.ODM_INFO("Updated %s image positions" % updated)
# Warn if a file path is specified but it does not exist
elif tree.odm_geo_file is not None and not os.path.isfile(tree.odm_geo_file):
log.ODM_WARNING("Image geolocation file %s does not exist" % tree.odm_geo_file)
# GPSDOP override if we have GPS accuracy information (such as RTK)
if 'gps_accuracy_is_set' in args:
@ -234,9 +231,9 @@ class ODMLoadDatasetStage(types.ODM_Stage):
item['p'].set_mask(os.path.basename(mask_file))
log.ODM_INFO("Wrote %s" % os.path.basename(mask_file))
else:
log.ODM_WARNING("Cannot generate mask for %s" % item['file'])
log.ODM_WARNING("Cannot generate mask for %s" % img)
except Exception as e:
log.ODM_WARNING("Cannot generate mask for %s: %s" % (item['file'], str(e)))
log.ODM_WARNING("Cannot generate mask for %s: %s" % (img, str(e)))
parallel_map(parallel_sky_filter, sky_images, max_workers=args.max_concurrency)

Wyświetl plik

@ -6,7 +6,6 @@ import fiona
import fiona.crs
import json
import zipfile
import math
from collections import OrderedDict
from pyproj import CRS
@ -126,35 +125,13 @@ class ODMGeoreferencingStage(types.ODM_Stage):
stages.append("transformation")
utmoffset = reconstruction.georef.utm_offset()
# Establish appropriate las scale for export
las_scale = 0.001
filtered_point_cloud_stats = tree.path("odm_filterpoints", "point_cloud_stats.json")
# Function that rounds to the nearest 10
# and then chooses the one below so our
# las scale is sensible
def powerr(r):
return pow(10,round(math.log10(r))) / 10
if os.path.isfile(filtered_point_cloud_stats):
try:
with open(filtered_point_cloud_stats, 'r') as stats:
las_stats = json.load(stats)
spacing = powerr(las_stats['spacing'])
log.ODM_INFO("las scale calculated as the minimum of 1/10 estimated spacing or %s, which ever is less." % las_scale)
las_scale = min(spacing, 0.001)
except Exception as e:
log.ODM_WARNING("Cannot find file point_cloud_stats.json. Using default las scale: %s" % las_scale)
else:
log.ODM_INFO("No point_cloud_stats.json found. Using default las scale: %s" % las_scale)
params += [
f'--filters.transformation.matrix="1 0 0 {utmoffset[0]} 0 1 0 {utmoffset[1]} 0 0 1 0 0 0 0 1"',
f'--writers.las.offset_x={reconstruction.georef.utm_east_offset}' ,
f'--writers.las.offset_y={reconstruction.georef.utm_north_offset}',
f'--writers.las.scale_x={las_scale}',
f'--writers.las.scale_y={las_scale}',
f'--writers.las.scale_z={las_scale}',
'--writers.las.scale_x=0.001',
'--writers.las.scale_y=0.001',
'--writers.las.scale_z=0.001',
'--writers.las.offset_z=0',
f'--writers.las.a_srs="{reconstruction.georef.proj4()}"' # HOBU this should maybe be WKT
]
@ -280,4 +257,3 @@ class ODMGeoreferencingStage(types.ODM_Stage):
os.remove(tree.filtered_point_cloud)

Wyświetl plik

@ -1,12 +1,9 @@
import os
import rasterio
from datetime import datetime
from osgeo import gdal
from opendm import io
from opendm import log
from opendm import types
from opendm import photo
from opendm.utils import copy_paths, get_processing_results_paths
from opendm.ogctiles import build_3dtiles
@ -17,25 +14,6 @@ class ODMPostProcess(types.ODM_Stage):
log.ODM_INFO("Post Processing")
rasters = [tree.odm_orthophoto_tif,
tree.path("odm_dem", "dsm.tif"),
tree.path("odm_dem", "dtm.tif")]
mean_capture_time = photo.find_mean_utc_time(reconstruction.photos)
mean_capture_dt = None
if mean_capture_time is not None:
mean_capture_dt = datetime.fromtimestamp(mean_capture_time).strftime('%Y:%m:%d %H:%M:%S') + '+00:00'
# Add TIFF tags
for product in rasters:
if os.path.isfile(product):
log.ODM_INFO("Adding TIFFTAGs to {}".format(product))
with rasterio.open(product, 'r+') as rst:
if mean_capture_dt is not None:
rst.update_tags(TIFFTAG_DATETIME=mean_capture_dt)
rst.update_tags(TIFFTAG_SOFTWARE='ODM {}'.format(log.odm_version()))
# GCP info
if not outputs['large']:
# TODO: support for split-merge?
@ -50,7 +28,9 @@ class ODMPostProcess(types.ODM_Stage):
with open(gcp_gml_export_file) as f:
gcp_xml = f.read()
for product in rasters:
for product in [tree.odm_orthophoto_tif,
tree.path("odm_dem", "dsm.tif"),
tree.path("odm_dem", "dtm.tif")]:
if os.path.isfile(product):
ds = gdal.Open(product)
if ds is not None:
@ -73,4 +53,3 @@ class ODMPostProcess(types.ODM_Stage):
copy_paths([os.path.join(args.project_path, p) for p in get_processing_results_paths()], args.copy_to, self.rerun())
except Exception as e:
log.ODM_WARNING("Cannot copy to %s: %s" % (args.copy_to, str(e)))

Wyświetl plik

@ -63,13 +63,11 @@ class ODMOpenMVSStage(types.ODM_Stage):
densify_ini_file = os.path.join(tree.openmvs, 'Densify.ini')
subres_levels = 2 # The number of lower resolutions to process before estimating output resolution depthmap.
filter_point_th = -20
min_resolution = 320 if args.pc_quality in ["low", "lowest"] else 640
config = [
"--resolution-level %s" % int(resolution_level),
'--dense-config-file "%s"' % densify_ini_file,
"--max-resolution %s" % int(outputs['undist_image_max_size']),
"--min-resolution %s" % min_resolution,
"--max-threads %s" % args.max_concurrency,
"--number-views-fuse %s" % number_views_fuse,
"--sub-resolution-levels %s" % subres_levels,

Wyświetl plik

@ -1,17 +1,21 @@
import os
import shutil
import json
import yaml
from opendm import log
from opendm.osfm import OSFMContext, get_submodel_argv, get_submodel_paths, get_all_submodel_paths
from opendm import types
from opendm import io
from opendm import system
from opendm import orthophoto
from opendm.dem import utils
from opendm.gcp import GCPFile
from opendm.dem import pdal, utils
from opendm.dem.merge import euclidean_merge_dems
from opensfm.large import metadataset
from opendm.cropper import Cropper
from opendm.concurrency import get_max_memory
from opendm.remote import LocalRemoteExecutor
from opendm.shots import merge_geojson_shots, merge_cameras
from opendm.shots import merge_geojson_shots
from opendm import point_cloud
from opendm.utils import double_quote
from opendm.tiles.tiler import generate_dem_tiles
@ -333,15 +337,6 @@ class ODMMergeStage(types.ODM_Stage):
else:
log.ODM_WARNING("Found merged shots.geojson in %s" % tree.odm_report)
# Merge cameras
cameras_json = tree.path("cameras.json")
if not io.file_exists(cameras_json) or self.rerun():
cameras_json_files = get_submodel_paths(tree.submodels_path, "cameras.json")
log.ODM_INFO("Merging %s cameras.json files" % len(cameras_json_files))
merge_cameras(cameras_json_files, cameras_json)
else:
log.ODM_WARNING("Found merged cameras.json in %s" % tree.root_path)
# Stop the pipeline short by skipping to the postprocess stage.
# Afterwards, we're done.
self.next_stage = self.last_stage()