From 5ec1b58a700c13f5e56ac13443cca4dfc199c1ab Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 13 Dec 2019 10:58:37 -0500 Subject: [PATCH] Extract band indexes Former-commit-id: 446b5338f396866ca3344b3e7b84444f638dde3b --- opendm/osfm.py | 3 ++- opendm/types.py | 57 +++++++++++++++++++++++++++++++-------------- tests/test_types.py | 30 +++++++++++++----------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/opendm/osfm.py b/opendm/osfm.py index 94659f50..6690a7c2 100644 --- a/opendm/osfm.py +++ b/opendm/osfm.py @@ -17,7 +17,8 @@ class OSFMContext: self.opensfm_project_path = opensfm_project_path def run(self, command): - system.run('%s/bin/opensfm %s "%s"' % + # Use Python 2.x by default, otherwise OpenSfM uses Python 3.x + system.run('/usr/bin/env python2 %s/bin/opensfm %s "%s"' % (context.opensfm_path, command, self.opensfm_project_path)) def is_reconstruction_done(self): diff --git a/opendm/types.py b/opendm/types.py index 079a45be..7652f79e 100644 --- a/opendm/types.py +++ b/opendm/types.py @@ -32,6 +32,7 @@ class ODM_Photo: self.longitude = None self.altitude = None self.band_name = 'RGB' + self.band_index = 0 # parse values from metadata self.parse_exif_values(path_file) @@ -41,9 +42,9 @@ class ODM_Photo: def __str__(self): - return '{} | camera: {} {} | dimensions: {} x {} | lat: {} | lon: {} | alt: {} | band: {}'.format( + return '{} | camera: {} {} | dimensions: {} x {} | lat: {} | lon: {} | alt: {} | band: {} ({})'.format( self.filename, self.camera_make, self.camera_model, self.width, self.height, - self.latitude, self.longitude, self.altitude, self.band_name) + self.latitude, self.longitude, self.altitude, self.band_name, self.band_index) def parse_exif_values(self, _path_file): # Disable exifread log @@ -72,13 +73,21 @@ class ODM_Photo: f.seek(0) xmp = self.get_xmp(f) - # Find band name (if available) + # Find band name and camera index (if available) + camera_index_tags = [ + 'DLS:SensorId', # Micasense + '@Camera:RigCameraIndex' # Parrot Sequoia + ] + for tags in xmp: if 'Camera:BandName' in tags: self.band_name = str(tags['Camera:BandName']).replace(" ", "") - break - + else: + for cit in camera_index_tags: + if cit in tags: + self.band_index = int(tags[cit]) self.width, self.height = get_image_size.get_image_size(_path_file) + print(self) # From https://github.com/mapillary/OpenSfM/blob/master/opensfm/exif.py def get_xmp(self, file): @@ -128,24 +137,36 @@ class ODM_Reconstruction(object): Looks at the reconstruction photos and determines if this is a single or multi-camera setup. """ - mc = {} + band_photos = {} + band_indexes = {} + for p in self.photos: - if not p.band_name in mc: - mc[p.band_name] = [] - mc[p.band_name].append(p) + if not p.band_name in band_photos: + band_photos[p.band_name] = [] + if not p.band_name in band_indexes: + band_indexes[p.band_name] = p.band_index + + band_photos[p.band_name].append(p) - bands_count = len(mc) + bands_count = len(band_photos) if bands_count >= 2 and bands_count <= 8: # Validate that all bands have the same number of images, # otherwise this is not a multi-camera setup - img_per_band = len(mc[p.band_name]) - for band in mc: - if len(mc[band]) != img_per_band: - log.ODM_ERROR("Multi-camera setup detected, but band \"%s\" (identified from \"%s\") has only %s images (instead of %s), perhaps images are missing or are corrupted. Please include all necessary files to process all bands and try again." % (band, mc[band][0].filename, len(mc[band]), img_per_band)) + img_per_band = len(band_photos[p.band_name]) + for band in band_photos: + if len(band_photos[band]) != img_per_band: + log.ODM_ERROR("Multi-camera setup detected, but band \"%s\" (identified from \"%s\") has only %s images (instead of %s), perhaps images are missing or are corrupted. Please include all necessary files to process all bands and try again." % (band, band_photos[band][0].filename, len(band_photos[band]), img_per_band)) raise RuntimeError("Invalid multi-camera images") + mc = [] + for band_name in band_indexes: + mc.append({'name': band_name, 'photos': band_photos[band_name]}) + + # Sort by band index + mc.sort(key=lambda x: band_indexes[x['name']]) + return mc - + return None def is_georeferenced(self): @@ -187,7 +208,7 @@ class ODM_Reconstruction(object): log.ODM_INFO("GCP file already exist: %s" % output_gcp_file) self.gcp = GCPFile(output_gcp_file) - self.georef = ODM_GeoRef.FromCoordsFile(output_coords_file) + self.georef = ODM_GeoRef.Froband_photosoordsFile(output_coords_file) return self.georef def georeference_with_gps(self, images_path, output_coords_file, rerun=False): @@ -197,7 +218,7 @@ class ODM_Reconstruction(object): else: log.ODM_INFO("Coordinates file already exist: %s" % output_coords_file) - self.georef = ODM_GeoRef.FromCoordsFile(output_coords_file) + self.georef = ODM_GeoRef.Froband_photosoordsFile(output_coords_file) except: log.ODM_WARNING('Could not generate coordinates file. An orthophoto will not be generated.') @@ -217,7 +238,7 @@ class ODM_GeoRef(object): return ODM_GeoRef(CRS.from_proj4(projstring)) @staticmethod - def FromCoordsFile(coords_file): + def Froband_photosoordsFile(coords_file): # check for coordinate file existence if not io.file_exists(coords_file): log.ODM_WARNING('Could not find file %s' % coords_file) diff --git a/tests/test_types.py b/tests/test_types.py index 6f7b6d49..0a0092d3 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -2,9 +2,10 @@ import unittest from opendm import types class ODMPhotoMock: - def __init__(self, filename, band_name): + def __init__(self, filename, band_name, band_index): self.filename = filename self.band_name = band_name + self.band_index = band_index def __str__(self): return "%s (%s)" % (self.filename, self.band_name) @@ -18,29 +19,30 @@ class TestTypes(unittest.TestCase): def test_reconstruction(self): # Multi camera setup - micasa_redsense_files = [('IMG_0298_1.tif', 'Red'), ('IMG_0298_2.tif', 'Green'), ('IMG_0298_3.tif', 'Blue'), ('IMG_0298_4.tif', 'NIR'), ('IMG_0298_5.tif', 'Rededge'), - ('IMG_0299_1.tif', 'Red'), ('IMG_0299_2.tif', 'Green'), ('IMG_0299_3.tif', 'Blue'), ('IMG_0299_4.tif', 'NIR'), ('IMG_0299_5.tif', 'Rededge'), - ('IMG_0300_1.tif', 'Red'), ('IMG_0300_2.tif', 'Green'), ('IMG_0300_3.tif', 'Blue'), ('IMG_0300_4.tif', 'NIR'), ('IMG_0300_5.tif', 'Rededge')] - photos = [ODMPhotoMock(f, b) for f, b in micasa_redsense_files] + micasa_redsense_files = [('IMG_0298_1.tif', 'Red', 1), ('IMG_0298_2.tif', 'Green', 2), ('IMG_0298_3.tif', 'Blue', 3), ('IMG_0298_4.tif', 'NIR', 4), ('IMG_0298_5.tif', 'Rededge', 5), + ('IMG_0299_1.tif', 'Red', 1), ('IMG_0299_2.tif', 'Green', 2), ('IMG_0299_3.tif', 'Blue', 3), ('IMG_0299_4.tif', 'NIR', 4), ('IMG_0299_5.tif', 'Rededge', 5), + ('IMG_0300_1.tif', 'Red', 1), ('IMG_0300_2.tif', 'Green', 2), ('IMG_0300_3.tif', 'Blue', 3), ('IMG_0300_4.tif', 'NIR', 4), ('IMG_0300_5.tif', 'Rededge', 5)] + photos = [ODMPhotoMock(f, b, i) for f, b, i in micasa_redsense_files] recon = types.ODM_Reconstruction(photos) self.assertTrue(recon.multi_camera is not None) # Found all 5 bands - for b in ["Red", "Blue", "Green", "NIR", "Rededge"]: - self.assertTrue(b in recon.multi_camera) - self.assertTrue([p.filename for p in recon.multi_camera["Red"]] == ['IMG_0298_1.tif', 'IMG_0299_1.tif', 'IMG_0300_1.tif']) - + bands = ["Red", "Green", "Blue", "NIR", "Rededge"] + for i in range(len(bands)): + self.assertEqual(bands[i], recon.multi_camera[i]['name']) + self.assertTrue([p.filename for p in recon.multi_camera[0]['photos']] == ['IMG_0298_1.tif', 'IMG_0299_1.tif', 'IMG_0300_1.tif']) + # Missing a file - micasa_redsense_files = [('IMG_0298_1.tif', 'Red'), ('IMG_0298_2.tif', 'Green'), ('IMG_0298_3.tif', 'Blue'), ('IMG_0298_4.tif', 'NIR'), ('IMG_0298_5.tif', 'Rededge'), - ('IMG_0299_2.tif', 'Green'), ('IMG_0299_3.tif', 'Blue'), ('IMG_0299_4.tif', 'NIR'), ('IMG_0299_5.tif', 'Rededge'), - ('IMG_0300_1.tif', 'Red'), ('IMG_0300_2.tif', 'Green'), ('IMG_0300_3.tif', 'Blue'), ('IMG_0300_4.tif', 'NIR'), ('IMG_0300_5.tif', 'Rededge')] - photos = [ODMPhotoMock(f, b) for f,b in micasa_redsense_files] + micasa_redsense_files = [('IMG_0298_1.tif', 'Red', 1), ('IMG_0298_2.tif', 'Green', 2), ('IMG_0298_3.tif', 'Blue', 3), ('IMG_0298_4.tif', 'NIR', 4), ('IMG_0298_5.tif', 'Rededge', 5), + ('IMG_0299_2.tif', 'Green', 2), ('IMG_0299_3.tif', 'Blue', 3), ('IMG_0299_4.tif', 'NIR', 4), ('IMG_0299_5.tif', 'Rededge', 5), + ('IMG_0300_1.tif', 'Red', 1), ('IMG_0300_2.tif', 'Green', 2), ('IMG_0300_3.tif', 'Blue', 3), ('IMG_0300_4.tif', 'NIR', 4), ('IMG_0300_5.tif', 'Rededge', 5)] + photos = [ODMPhotoMock(f, b, i) for f,b,i in micasa_redsense_files] self.assertRaises(RuntimeError, types.ODM_Reconstruction, photos) # Single camera dji_files = ['DJI_0018.JPG','DJI_0019.JPG','DJI_0020.JPG','DJI_0021.JPG','DJI_0022.JPG','DJI_0023.JPG'] - photos = [ODMPhotoMock(f, 'RGB') for f in dji_files] + photos = [ODMPhotoMock(f, 'RGB', 0) for f in dji_files] recon = types.ODM_Reconstruction(photos) self.assertTrue(recon.multi_camera is None)