diff --git a/.travis.yml b/.travis.yml index 09c5597c..d1314c72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,10 @@ before_install: # install library dependencies - sudo apt-get install libzbar0 make perl # Install exiftool - - wget https://cpan.metacpan.org/authors/id/E/EX/EXIFTOOL/Image-ExifTool-12.15.tar.gz - - tar -xzf Image-ExifTool-12.15.tar.gz - - pushd Image-ExifTool-12.15/ - - perl Makefile.PL + - wget https://cpan.metacpan.org/authors/id/E/EX/EXIFTOOL/Image-ExifTool-12.76.tar.gz + - tar -xzf Image-ExifTool-12.76.tar.gz + - pushd Image-ExifTool-12.76/ + - perl Makefile.PL - make test - sudo make install - popd diff --git a/micasense/image.py b/micasense/image.py index dd31625d..d12fb569 100644 --- a/micasense/image.py +++ b/micasense/image.py @@ -65,7 +65,7 @@ class Image(object): band of multispectral information """ - def __init__(self, image_path, exiftool_obj=None, allow_uncalibrated=False): + def __init__(self, image_path: str, exiftool_obj=None, allow_uncalibrated=False): if not os.path.isfile(image_path): raise IOError("Provided path is not a file: {}".format(image_path)) self.path = image_path @@ -191,7 +191,7 @@ def compute_horizontal_irradiance_dls1(self): return self.horizontal_irradiance_from_direct_scattered() def compute_horizontal_irradiance_dls2(self): - """ Compute the proper solar elevation, solar azimuth, and horizontal irradiance + """ Compute the proper solar elevation, solar azimuth, and horizontal irradiance for cases where the camera system did not do it correctly """ _, _, _, \ self.solar_elevation, \ @@ -373,7 +373,7 @@ def vignette(self): yv = y.T / y_dim k = self.vignette_polynomial2D e = self.vignette_polynomial2Dexponents - p2 = np.zeros_like(xv, dtype=np.float) + p2 = np.zeros_like(xv, dtype=float) for i, c in enumerate(k): ex = e[2 * i] ey = e[2 * i + 1] diff --git a/micasense/imageset.py b/micasense/imageset.py index 6b5a08d8..035c2bd2 100644 --- a/micasense/imageset.py +++ b/micasense/imageset.py @@ -67,7 +67,7 @@ def from_directory(cls, directory, progress_callback=None, exiftool_path=None, a if exiftool_path is None and os.environ.get('exiftoolpath') is not None: exiftool_path = os.path.normpath(os.environ.get('exiftoolpath')) - with exiftool.ExifTool(exiftool_path) as exift: + with exiftool.ExifToolHelper(exiftool_path) as exift: for i, path in enumerate(matches): images.append(image.Image(path, exiftool_obj=exift, allow_uncalibrated=allow_uncalibrated)) if progress_callback is not None: diff --git a/micasense/imageutils.py b/micasense/imageutils.py index 0c88d4d1..4f163485 100644 --- a/micasense/imageutils.py +++ b/micasense/imageutils.py @@ -509,7 +509,7 @@ def min_max(pts): def map_points(pts, image_size, warpMatrix, distortion_coeffs, camera_matrix, warp_mode=cv2.MOTION_HOMOGRAPHY): # extra dimension makes opencv happy - pts = np.array([pts], dtype=np.float) + pts = np.array([pts], dtype=float) new_cam_mat, _ = cv2.getOptimalNewCameraMatrix(camera_matrix, distortion_coeffs, image_size, 1) new_pts = cv2.undistortPoints(pts, camera_matrix, distortion_coeffs, P=new_cam_mat) if warp_mode == cv2.MOTION_AFFINE: @@ -584,7 +584,7 @@ def radiometric_pan_sharpen(capture, warp_matrices=None, panchro_band=5, irradia # this function performs a radiometrically accurate pansharpening on the input capture # if no warp matrices are supplied to align the multispec images to the pan band # the camera calibration is used (which produces reasonably well aligned imagers in most cases) - # in addition to the pan sharpened stack, the equivalent upsampled stack is also produced + # in addition to the pan sharpened stack, the equivalent upsampled stack is also produced # for comparison # use the warp matrices we have for the stack, if not user supplied if warp_matrices is None: @@ -654,9 +654,9 @@ def radiometric_pan_sharpen(capture, warp_matrices=None, panchro_band=5, irradia def prepare_exif_for_stacks(thecapture, thefilename): lat, lon, alt = thecapture.location() resolution = thecapture.images[0].focal_plane_resolution_px_per_mm - # eventually it would be nice to add the capture ID, flight ID, + # eventually it would be nice to add the capture ID, flight ID, # and yaw/pitch/roll, but these are non-standard exif tags, - # so it is more difficult. + # so it is more difficult. attitude = thecapture.dls_pose() theid = thecapture.uuid flightid = thecapture.flightid diff --git a/micasense/metadata.py b/micasense/metadata.py index 051b8350..eda06fbf 100644 --- a/micasense/metadata.py +++ b/micasense/metadata.py @@ -36,7 +36,7 @@ class Metadata(object): """ Container for Micasense image metadata""" - def __init__(self, filename, exiftool_path=None, exiftool_obj=None): + def __init__(self, filename: str, exiftool_path=None, exiftool_obj=None): if exiftool_obj is not None: self.exif = exiftool_obj.get_metadata(filename) return @@ -48,7 +48,7 @@ def __init__(self, filename, exiftool_path=None, exiftool_obj=None): self.exiftoolPath = None if not os.path.isfile(filename): raise IOError("Input path is not a file") - with exiftool.ExifTool(self.exiftoolPath) as exift: + with exiftool.ExifToolHelper() as exift: self.exif = exift.get_metadata(filename) def get_all(self): @@ -59,10 +59,11 @@ def get_item(self, item, index=None): """ Get metadata item by Namespace:Parameter""" val = None try: - val = self.exif[item] + assert len(self.exif) > 0 + val = self.exif[0][item] if index is not None: try: - if isinstance(val, unicode): + if isinstance(val, pytz.unicode): val = val.encode('ascii', 'ignore') except NameError: # throws on python 3 where unicode is undefined @@ -83,7 +84,7 @@ def size(self, item): """get the size (length) of a metadata item""" val = self.get_item(item) try: - if isinstance(val, unicode): + if isinstance(val, pytz.unicode): val = val.encode('ascii', 'ignore') except NameError: # throws on python 3 where unicode is undefined @@ -132,7 +133,6 @@ def utc_time(self): if subsec < 0: negative = -1.0 subsec *= -1.0 - subsec = float('0.{}'.format(int(subsec))) subsec *= negative ms = subsec * 1e3 utc_time += timedelta(milliseconds=ms) @@ -336,7 +336,7 @@ def horizontal_irradiance_valid(self): return version.parse(version_string) >= version.parse(good_version) def spectral_irradiance(self): - """ Raw spectral irradiance measured by an irradiance sensor. + """ Raw spectral irradiance measured by an irradiance sensor. Calibrated to W/m^2/nm using irradiance_scale_factor, but not corrected for angles """ return self.__float_or_zero(self.get_item('XMP:SpectralIrradiance')) * self.irradiance_scale_factor() @@ -379,7 +379,7 @@ def auto_calibration_image(self): self.panel_serial() is not None def panel_albedo(self): - """ Surface albedo of the active portion of the reflectance panel as calculated by the camera + """ Surface albedo of the active portion of the reflectance panel as calculated by the camera (usually from the information in the panel QR code) """ albedo = self.get_item('XMP:Albedo') if albedo is not None: diff --git a/micasense/utils.py b/micasense/utils.py index b95421b6..65fec77d 100755 --- a/micasense/utils.py +++ b/micasense/utils.py @@ -128,10 +128,10 @@ def correct_lens_distortion(meta, image): ndistortion = meta.size('XMP:PerspectiveDistortion') distortion_parameters = np.array([float(meta.get_item('XMP:PerspectiveDistortion', i)) for i in range(ndistortion)]) # get the two principal points - pp = np.array(meta.get_item('XMP:PrincipalPoint').split(',')).astype(np.float) + pp = np.array(meta.get_item('XMP:PrincipalPoint').split(',')).astype(float) # values in pp are in [mm] and need to be rescaled to pixels - focal_plane_x_resolution = float(meta.get_item('EXIF:focal_plane_x_resolution')) - focal_plane_y_resolution = float(meta.get_item('EXIF:focal_plane_y_resolution')) + focal_plane_x_resolution = float(meta.get_item('EXIF:FocalPlaneXResolution')) + focal_plane_y_resolution = float(meta.get_item('EXIF:FocalPlaneYResolution')) cX = pp[0] * focal_plane_x_resolution cY = pp[1] * focal_plane_y_resolution diff --git a/tests/conftest.py b/tests/conftest.py index 4b76a3e4..6203b815 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,7 +24,7 @@ """ import glob -import os +from pathlib import Path import pytest @@ -35,39 +35,39 @@ @pytest.fixture() def files_dir(): - return os.path.join('data', 'REDEDGE-MX') + return Path(__file__).parent.parent/'data'/'REDEDGE-MX' @pytest.fixture() def altum_files_dir(): - return os.path.join('data', 'ALTUM') + return Path(__file__).parent.parent/'data'/'ALTUM' @pytest.fixture() def ten_band_files_dir(): - return os.path.join('data', 'REDEDGE-MX-DUAL') + return Path(__file__).parent.parent/'data'/'REDEDGE-MX-DUAL' @pytest.fixture() -def panel_rededge_file_list(files_dir): - return glob.glob(os.path.join(files_dir, 'IMG_0001_*.tif')) +def panel_rededge_file_list(files_dir: Path): + return glob.glob(str(files_dir/'IMG_0001_*.tif')) @pytest.fixture() -def non_panel_rededge_file_list(files_dir): - return glob.glob(os.path.join(files_dir, 'IMG_0020_*.tif')) +def non_panel_rededge_file_list(files_dir: Path): + return glob.glob(str(files_dir/'IMG_0020_*.tif')) @pytest.fixture() -def bad_file_list(files_dir): - file1 = os.path.join(files_dir, 'IMG_0020_1.tif') - file2 = os.path.join(files_dir, 'IMG_0001_1.tif') +def bad_file_list(files_dir: Path): + file1 = str(files_dir/'IMG_0020_1.tif') + file2 = str(files_dir/'IMG_0001_1.tif') return [file1, file2] @pytest.fixture() def panel_altum_file_list(altum_files_dir): - return glob.glob(os.path.join(altum_files_dir, 'IMG_0000_*.tif')) + return glob.glob(str(altum_files_dir/'IMG_0000_*.tif')) @pytest.fixture() @@ -81,13 +81,13 @@ def non_panel_rededge_capture(non_panel_rededge_file_list): @pytest.fixture() -def panel_10band_rededge_file_list(ten_band_files_dir): - return glob.glob(os.path.join(ten_band_files_dir, 'IMG_0001_*.tif')) +def panel_10band_rededge_file_list(ten_band_files_dir: Path): + return glob.glob(str(ten_band_files_dir/'IMG_0001_*.tif')) @pytest.fixture() -def flight_10band_rededge_file_list(ten_band_files_dir): - return glob.glob(os.path.join(ten_band_files_dir, 'IMG_0007_*.tif')) +def flight_10band_rededge_file_list(ten_band_files_dir: Path): + return glob.glob(str(ten_band_files_dir/'IMG_0007_*.tif')) @pytest.fixture() @@ -97,8 +97,8 @@ def panel_altum_capture(panel_altum_file_list): @pytest.fixture() -def non_panel_altum_file_list(altum_files_dir): - return glob.glob(os.path.join(altum_files_dir, 'IMG_0021_*.tif')) +def non_panel_altum_file_list(altum_files_dir: Path): + return glob.glob(str(altum_files_dir/'IMG_0021_*.tif')) @pytest.fixture() @@ -109,50 +109,50 @@ def non_panel_altum_capture(non_panel_altum_file_list): @pytest.fixture() def panel_image_name(): - image_path = os.path.join('data', 'REDEDGE-MX') - return os.path.join(image_path, 'IMG_0001_1.tif') + image_path = Path(__file__).parent.parent/'data'/'REDEDGE-MX' + return str(image_path/'IMG_0001_1.tif') @pytest.fixture() def panel_image_name_red(): - image_path = os.path.join('data', 'REDEDGE-MX') - return os.path.join(image_path, 'IMG_0001_2.tif') + image_path = Path(__file__).parent.parent/'data'/'REDEDGE-MX' + return str(image_path/'IMG_0001_2.tif') @pytest.fixture() def flight_image_name(): - image_path = os.path.join('data', 'REDEDGE-MX') - return os.path.join(image_path, 'IMG_0020_1.tif') + image_path = Path(__file__).parent.parent/'data'/'REDEDGE-MX' + return str(image_path/'IMG_0020_1.tif') @pytest.fixture() -def altum_panel_image_name(altum_files_dir): - return os.path.join(altum_files_dir, 'IMG_0000_1.tif') +def altum_panel_image_name(altum_files_dir: Path): + return str(altum_files_dir/'IMG_0000_1.tif') @pytest.fixture() -def altum_lwir_image_name(altum_files_dir): - return os.path.join(altum_files_dir, 'IMG_0000_6.tif') +def altum_lwir_image_name(altum_files_dir: Path): + return str(altum_files_dir/'IMG_0000_6.tif') @pytest.fixture() -def altum_flight_image_name(altum_files_dir): - return os.path.join(altum_files_dir, 'IMG_0021_1.tif') +def altum_flight_image_name(altum_files_dir: Path): + return str(altum_files_dir/'IMG_0021_1.tif') @pytest.fixture() -def img(files_dir): - return image.Image(os.path.join(files_dir, 'IMG_0001_1.tif')) +def img(files_dir: Path): + return image.Image(str(files_dir/'IMG_0001_1.tif')) @pytest.fixture() -def img2(files_dir): - return image.Image(os.path.join(files_dir, 'IMG_0001_2.tif')) +def img2(files_dir: Path): + return image.Image(str(files_dir/'IMG_0001_2.tif')) @pytest.fixture() def panel_altum_file_name(altum_files_dir): - return os.path.join(altum_files_dir, 'IMG_0000_1.tif') + return str(altum_files_dir/'IMG_0000_1.tif') @pytest.fixture() @@ -166,31 +166,31 @@ def altum_flight_image(altum_flight_image_name): @pytest.fixture() -def non_existant_file_name(altum_files_dir): - return os.path.join(altum_files_dir, 'NOFILE.tif') +def non_existant_file_name(altum_files_dir: Path): + return str(altum_files_dir/'NOFILE.tif') @pytest.fixture() -def altum_lwir_image(altum_files_dir): - return image.Image(os.path.join(altum_files_dir, 'IMG_0000_6.tif')) +def altum_lwir_image(altum_files_dir: Path): + return image.Image(str(altum_files_dir/'IMG_0000_6.tif')) @pytest.fixture() def meta(): - image_path = os.path.join('data', 'REDEDGE-MX') - return metadata.Metadata(os.path.join(image_path, 'IMG_0001_1.tif')) + image_path = Path(__file__).parent.parent/'data'/'REDEDGE-MX'/'IMG_0001_1.tif' + return metadata.Metadata(str(image_path)) @pytest.fixture() def meta_v3(): - image_path = os.path.join('data', 'REDEDGE-MX') - return metadata.Metadata(os.path.join(image_path, 'IMG_0020_4.tif')) + image_path = Path(__file__).parent.parent/'data'/'REDEDGE-MX' + return metadata.Metadata(str(image_path/'IMG_0020_4.tif')) @pytest.fixture() def meta_bad_exposure(): - image_path = os.path.join('data', 'REDEDGE-MX') - return metadata.Metadata(os.path.join(image_path, 'IMG_0020_1.tif')) + image_path = Path(__file__).parent.parent/'data'/'REDEDGE-MX' + return metadata.Metadata(str(image_path/'IMG_0020_1.tif')) @pytest.fixture() @@ -200,8 +200,8 @@ def meta_altum_dls2(altum_flight_image_name): @pytest.fixture() def bad_dls2_horiz_irr_image(): - image_path = os.path.join('data', 'ALTUM') - return image.Image(os.path.join(image_path, 'IMG_0021_1.tif')) + image_path = Path(__file__).parent.parent/'data'/'ALTUM' + return image.Image(str(image_path/'IMG_0021_1.tif')) @pytest.fixture() diff --git a/tests/test_imageset.py b/tests/test_imageset.py index fd002670..ec9f3949 100644 --- a/tests/test_imageset.py +++ b/tests/test_imageset.py @@ -24,6 +24,7 @@ """ import os +from pathlib import Path import pytest @@ -33,7 +34,7 @@ @pytest.fixture() def files_dir(): - return os.path.join('data', 'REDEDGE-MX') + return Path(__file__).parent.parent/'data'/'REDEDGE-MX' progress_val = 0.0