diff --git a/README.md b/README.md index c3b1ba42..fbf7a199 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The easiest way to run ODM is via docker. To install docker, see [docs.docker.co docker pull opendronemap/odm ``` -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: +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: ```bash # Windows diff --git a/SuperBuild/CMakeLists.txt b/SuperBuild/CMakeLists.txt index c558894a..3716834d 100644 --- a/SuperBuild/CMakeLists.txt +++ b/SuperBuild/CMakeLists.txt @@ -244,7 +244,7 @@ externalproject_add(dem2points externalproject_add(odm_orthophoto DEPENDS opencv GIT_REPOSITORY https://github.com/OpenDroneMap/odm_orthophoto.git - GIT_TAG 353 + GIT_TAG 355 PREFIX ${SB_BINARY_DIR}/odm_orthophoto SOURCE_DIR ${SB_SOURCE_DIR}/odm_orthophoto CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR} diff --git a/SuperBuild/cmake/External-OpenMVS.cmake b/SuperBuild/cmake/External-OpenMVS.cmake index cad212f9..188dbfbc 100644 --- a/SuperBuild/cmake/External-OpenMVS.cmake +++ b/SuperBuild/cmake/External-OpenMVS.cmake @@ -53,7 +53,7 @@ ExternalProject_Add(${_proj_name} #--Download step-------------- DOWNLOAD_DIR ${SB_DOWNLOAD_DIR} GIT_REPOSITORY https://github.com/OpenDroneMap/openMVS - GIT_TAG 320 + GIT_TAG 355 #--Update/Patch step---------- UPDATE_COMMAND "" #--Configure step------------- diff --git a/SuperBuild/cmake/External-OpenSfM.cmake b/SuperBuild/cmake/External-OpenSfM.cmake index 6ed2e1f4..f50c8e85 100644 --- a/SuperBuild/cmake/External-OpenSfM.cmake +++ b/SuperBuild/cmake/External-OpenSfM.cmake @@ -25,7 +25,7 @@ ExternalProject_Add(${_proj_name} #--Download step-------------- DOWNLOAD_DIR ${SB_DOWNLOAD_DIR} GIT_REPOSITORY https://github.com/OpenDroneMap/OpenSfM/ - GIT_TAG 352 + GIT_TAG 355 #--Update/Patch step---------- UPDATE_COMMAND git submodule update --init --recursive #--Configure step------------- diff --git a/VERSION b/VERSION index 65afb3b8..7d280e2c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.5.4 +3.5.5 diff --git a/opendm/ai.py b/opendm/ai.py index eab76499..2bd95f24 100644 --- a/opendm/ai.py +++ b/opendm/ai.py @@ -4,6 +4,24 @@ from opendm import log import zipfile import time import sys +import rawpy + +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(".", "_") diff --git a/opendm/bgfilter.py b/opendm/bgfilter.py index 3dfd818e..5285535c 100644 --- a/opendm/bgfilter.py +++ b/opendm/bgfilter.py @@ -5,6 +5,7 @@ import cv2 import os import onnxruntime as ort from opendm import log +from opendm.ai import read_image from threading import Lock mutex = Lock() @@ -73,11 +74,7 @@ class BgFilter(): return output def run_img(self, img_path, dest): - img = cv2.imread(img_path, cv2.IMREAD_COLOR) - if img is None: - return None - - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + img = read_image(img_path) mask = self.get_mask(img) img_name = os.path.basename(img_path) diff --git a/opendm/context.py b/opendm/context.py index 5c4b7a5c..72d8cc06 100644 --- a/opendm/context.py +++ b/opendm/context.py @@ -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'} +supported_extensions = {'.jpg','.jpeg','.png', '.tif', '.tiff', '.bmp', '.raw', '.dng', '.nef'} supported_video_extensions = {'.mp4', '.mov', '.lrv', '.ts'} # Define the number of cores diff --git a/opendm/get_image_size.py b/opendm/get_image_size.py index 94b60a52..2bc44276 100644 --- a/opendm/get_image_size.py +++ b/opendm/get_image_size.py @@ -1,6 +1,6 @@ from PIL import Image import cv2 - +import rawpy from opendm import log Image.MAX_IMAGE_PIXELS = None @@ -9,12 +9,18 @@ def get_image_size(file_path, fallback_on_error=True): """ Return (width, height) for a given img file """ + try: - with Image.open(file_path) as img: - width, height = img.size + 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 except Exception as e: if fallback_on_error: - log.ODM_WARNING("Cannot read %s with PIL, fallback to cv2: %s" % (file_path, str(e))) + log.ODM_WARNING("Cannot read %s with image library, fallback to cv2: %s" % (file_path, str(e))) img = cv2.imread(file_path) width = img.shape[1] height = img.shape[0] diff --git a/opendm/multispectral.py b/opendm/multispectral.py index 19767cd4..953bef6c 100644 --- a/opendm/multispectral.py +++ b/opendm/multispectral.py @@ -273,7 +273,10 @@ 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: diff --git a/opendm/orthophoto.py b/opendm/orthophoto.py index 319a504c..d413424e 100644 --- a/opendm/orthophoto.py +++ b/opendm/orthophoto.py @@ -43,33 +43,49 @@ def generate_png(orthophoto_file, output_file=None, outsize=None): output_file = base + '.png' # See if we need to select top three bands - bandparam = "" + params = [] - gtif = gdal.Open(orthophoto_file) - if gtif.RasterCount > 4: + try: + gtif = gdal.Open(orthophoto_file) bands = [] for idx in range(1, gtif.RasterCount+1): bands.append(gtif.GetRasterBand(idx).GetColorInterpretation()) bands = dict(zip(bands, range(1, len(bands)+1))) - try: + if gtif.RasterCount >= 3: 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: - raise Exception("Cannot find bands") + params.append("-b %s -b %s -b %s" % (red, green, blue)) + else: + params.append("-b 1 -b 2 -b 3") + 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") - 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 + 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)) - osparam = "" if outsize is not None: - osparam = "-outsize %s 0" % outsize + params.append("-outsize %s 0" % outsize) - system.run('gdal_translate -of png "%s" "%s" %s %s ' - '--config GDAL_CACHEMAX %s%% ' % (orthophoto_file, output_file, osparam, bandparam, get_max_memory())) + system.run('gdal_translate -of png "%s" "%s" %s ' + '--config GDAL_CACHEMAX %s%% ' % (orthophoto_file, output_file, " ".join(params), get_max_memory())) def generate_kmz(orthophoto_file, output_file=None, outsize=None): if output_file is None: diff --git a/opendm/skyremoval/skyfilter.py b/opendm/skyremoval/skyfilter.py index 56fee610..bee0dce9 100644 --- a/opendm/skyremoval/skyfilter.py +++ b/opendm/skyremoval/skyfilter.py @@ -6,6 +6,7 @@ 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() @@ -72,11 +73,7 @@ class SkyFilter(): def run_img(self, img_path, dest): - img = cv2.imread(img_path, cv2.IMREAD_COLOR) - if img is None: - return None - - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + img = read_image(img_path) img = np.array(img / 255., dtype=np.float32) mask = self.get_mask(img) diff --git a/requirements.txt b/requirements.txt index 844668f1..dbfa3599 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,6 +23,7 @@ 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