-
Notifications
You must be signed in to change notification settings - Fork 175
Reconcile save capture 110 #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
poynting
wants to merge
5
commits into
master
Choose a base branch
from
reconcile-save-capture-110
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
aea4b4c
reconciling save_capture with other changes
poynting 2784daa
Merge branch 'mdanilevicz-save_capture' into reconcile-save-capture-110
poynting 8bdca50
Force img_type to be either 'reflectance' or 'radiance'. Add out_data…
and-viceversa 2f50749
Update process_imageset() parameters, docstrings, and warning checks …
and-viceversa eaed830
Fix LWIR UInt_16 scale
and-viceversa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ | |
|
||
import math | ||
import os | ||
import warnings | ||
|
||
import cv2 | ||
import imageio | ||
|
@@ -38,6 +39,8 @@ | |
import micasense.imageutils as imageutils | ||
import micasense.plotutils as plotutils | ||
|
||
warnings.simplefilter(action="once") | ||
|
||
|
||
class Capture(object): | ||
""" | ||
|
@@ -80,6 +83,7 @@ def __init__(self, images, panel_corners=None): | |
else: | ||
self.panel_corners = panel_corners | ||
|
||
self.__image_type = None | ||
self.__aligned_capture = None | ||
|
||
def set_panel_corners(self, panel_corners): | ||
|
@@ -201,6 +205,7 @@ def clear_image_data(self): | |
for img in self.images: | ||
img.clear_image_data() | ||
self.__aligned_capture = None | ||
self.__image_type = None | ||
|
||
def center_wavelengths(self): | ||
"""Returns a list of the image center wavelengths in nanometers.""" | ||
|
@@ -468,26 +473,39 @@ def get_warp_matrices(self, ref_index=None): | |
warp_matrices = [np.linalg.inv(im.get_homography(ref)) for im in self.images] | ||
return [w / w[2, 2] for w in warp_matrices] | ||
|
||
def create_aligned_capture(self, irradiance_list=None, warp_matrices=None, normalize=False, img_type=None, | ||
def create_aligned_capture(self, img_type, irradiance_list=None, warp_matrices=None, normalize=False, | ||
motion_type=cv2.MOTION_HOMOGRAPHY): | ||
""" | ||
Creates aligned Capture. Computes undistorted radiance or reflectance images if necessary. | ||
:param img_type: str 'radiance' or 'reflectance' depending on image metadata. | ||
:param irradiance_list: List of mean panel region irradiance. | ||
:param warp_matrices: 2d List of warp matrices derived from Capture.get_warp_matrices() | ||
:param normalize: FIXME: This parameter isn't used? | ||
:param img_type: str 'radiance' or 'reflectance' depending on image metadata. | ||
:param motion_type: OpenCV import. Also know as warp_mode. MOTION_HOMOGRAPHY or MOTION_AFFINE. | ||
For Altum images only use HOMOGRAPHY. | ||
:return: ndarray with alignment changes | ||
""" | ||
if img_type is None and irradiance_list is None and self.dls_irradiance() is None: | ||
if img_type == 'radiance': | ||
self.compute_undistorted_radiance() | ||
img_type = 'radiance' | ||
elif img_type is None: | ||
if irradiance_list is None: | ||
self.__image_type = 'radiance' | ||
elif img_type == 'reflectance': | ||
# TODO: Handle pre-flight Panel cap + post-flight Panel cap + DLS values | ||
# Add use_dls or similar user option to properly scale irradiance_list in different configurations. | ||
# Alternatively if this should be done elsewhere then dls_irradiance() should probably not be used here. | ||
|
||
# if no irradiance values provided, attempt to use DLS | ||
if irradiance_list is None and self.dls_present(): | ||
irradiance_list = self.dls_irradiance() + [0] | ||
elif irradiance_list is None and not self.dls_present(): | ||
raise RuntimeError('Reflectance output requested, but no irradiance values given and no DLS values ' | ||
'found in image metadata.') | ||
|
||
self.compute_undistorted_reflectance(irradiance_list) | ||
img_type = 'reflectance' | ||
self.__image_type = 'reflectance' | ||
else: | ||
raise RuntimeError('Unknown img_type output requested: {}\nMust be "radiance" or "reflectance".' | ||
.format(img_type)) | ||
|
||
if warp_matrices is None: | ||
warp_matrices = self.get_warp_matrices() | ||
cropped_dimensions, _ = imageutils.find_crop_bounds(self, warp_matrices, warp_mode=motion_type) | ||
|
@@ -508,25 +526,46 @@ def aligned_shape(self): | |
raise RuntimeError("Call Capture.create_aligned_capture() prior to saving as stack.") | ||
return self.__aligned_capture.shape | ||
|
||
def save_capture_as_stack(self, out_file_name, sort_by_wavelength=False, photometric='MINISBLACK'): | ||
def save_capture_as_stack(self, out_file_name, out_data_type='GDT_UInt16', | ||
sort_by_wavelength=False, photometric='MINISBLACK'): | ||
""" | ||
Output the Images in the Capture object as GTiff image stack. | ||
:param out_file_name: str system file path | ||
:param out_data_type: str GDT_Float32 or GDT_UInt16 | ||
Default: GDT_UInt16 will write images as scaled reflectance values. EO (32769=100%) | ||
and LWIR in centi-Kelvin (0-65535). | ||
GDT_Float32 will write images as floating point reflectance values. EO (1.0=100%) | ||
and LWIR in floating point Celsius. | ||
https://gdal.org/api/raster_c_api.html#_CPPv412GDALDataType | ||
:param sort_by_wavelength: boolean | ||
:param photometric: str GDAL argument for GTiff color matching | ||
""" | ||
from osgeo.gdal import GetDriverByName, GDT_UInt16 | ||
from osgeo.gdal import GetDriverByName, GDT_UInt16, GDT_Float32 | ||
if self.__aligned_capture is None: | ||
raise RuntimeError("Call Capture.create_aligned_capture() prior to saving as stack.") | ||
|
||
rows, cols, bands = self.__aligned_capture.shape | ||
driver = GetDriverByName('GTiff') | ||
|
||
out_raster = driver.Create(out_file_name, cols, rows, bands, GDT_UInt16, | ||
# force correct output datatype | ||
if self.__image_type == 'radiance': | ||
gdal_type = GDT_Float32 # force floating point values for radiance and degrees C | ||
elif self.__image_type == 'reflectance' and out_data_type == 'GDT_UInt16': | ||
gdal_type = GDT_UInt16 | ||
elif self.__image_type == 'reflectance' and out_data_type == 'GDT_Float32': | ||
gdal_type = GDT_Float32 | ||
else: | ||
warnings.warn(message='Output data type in Capture.save_capture_as_bands() was called as {}. ' | ||
'Must use "GDT_UInt16" or "GDT_Float32". Defaulting to GDT_UInt16...' | ||
.format(out_data_type), | ||
category=UserWarning) | ||
gdal_type = GDT_UInt16 | ||
|
||
out_raster = driver.Create(out_file_name, cols, rows, bands, gdal_type, | ||
options=['INTERLEAVE=BAND', 'COMPRESS=DEFLATE', f'PHOTOMETRIC={photometric}']) | ||
try: | ||
if out_raster is None: | ||
raise IOError("could not load gdal GeoTiff driver") | ||
raise IOError("Could not load GDAL GeoTiff driver.") | ||
|
||
if sort_by_wavelength: | ||
eo_list = list(np.argsort(np.array(self.center_wavelengths())[self.eo_indices()])) | ||
|
@@ -538,20 +577,87 @@ def save_capture_as_stack(self, out_file_name, sort_by_wavelength=False, photome | |
out_data = self.__aligned_capture[:, :, in_band] | ||
out_data[out_data < 0] = 0 | ||
out_data[out_data > 2] = 2 # limit reflectance data to 200% to allow some specular reflections | ||
out_band.WriteArray(out_data * 32768) # scale reflectance images so 100% = 32768 | ||
# scale reflectance images so 100% = 32768 | ||
out_band.WriteArray(out_data * 32768 if gdal_type == 2 else out_data) | ||
out_band.FlushCache() | ||
|
||
for out_band, in_band in enumerate(self.lw_indices()): | ||
out_band = out_raster.GetRasterBand(len(eo_list) + out_band + 1) | ||
# scale data from float degC to back to centi-Kelvin to fit into uint16 | ||
out_data = (self.__aligned_capture[:, :, in_band] + 273.15) * 100 | ||
out_data[out_data < 0] = 0 | ||
out_data[out_data > 65535] = 65535 | ||
out_data = (self.__aligned_capture[:, :, in_band] + 273.15) * 100 if gdal_type == 2 \ | ||
else self.__aligned_capture[:, :, in_band] | ||
if gdal_type == 2: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be |
||
out_data[out_data < 0] = 0 | ||
out_data[out_data > 65535] = 65535 | ||
out_band.WriteArray(out_data) | ||
out_band.FlushCache() | ||
finally: | ||
out_raster = None | ||
|
||
def save_capture_as_bands(self, out_file_name, out_data_type='GDT_UInt16', photometric='MINISBLACK'): | ||
""" | ||
Output the Images in the Capture object as separate GTiffs. | ||
:param out_file_name: str system file path without file extension | ||
:param out_data_type: str GDT_Float32 or GDT_UInt16 | ||
Default: GDT_UInt16 will write images as scaled reflectance values. EO (32769=100%) | ||
and LWIR in centi-Kelvin (0-65535). | ||
GDT_Float32 will write images as floating point reflectance values. EO (1.0=100%) | ||
and LWIR in floating point Celsius. | ||
https://gdal.org/api/raster_c_api.html#_CPPv412GDALDataType | ||
:param photometric: str GDAL argument for GTiff color matching | ||
:return: None | ||
""" | ||
from osgeo.gdal import GetDriverByName, GDT_UInt16, GDT_Float32 | ||
if self.__aligned_capture is None: | ||
raise RuntimeError("Call Capture.create_aligned_capture() prior to saving as stack.") | ||
|
||
# force correct output datatype | ||
if self.__image_type == 'radiance': | ||
gdal_type = GDT_Float32 # force floating point values for radiance and degrees C | ||
elif self.__image_type == 'reflectance' and out_data_type == 'GDT_UInt16': | ||
gdal_type = GDT_UInt16 | ||
elif self.__image_type == 'reflectance' and out_data_type == 'GDT_Float32': | ||
gdal_type = GDT_Float32 | ||
else: | ||
warnings.warn(message='Output data type in Capture.save_capture_as_bands() was called as {}. ' | ||
'Must use "GDT_UInt16" or "GDT_Float32". Defaulting to GDT_UInt16...' | ||
.format(out_data_type), | ||
category=UserWarning) | ||
gdal_type = GDT_UInt16 | ||
|
||
# predictably handle accidental .tif.tif values | ||
if out_file_name.endswith('.tif'): | ||
out_file_path = out_file_name[:-4] | ||
else: | ||
out_file_path = out_file_name | ||
|
||
rows, cols, bands = self.__aligned_capture.shape | ||
driver = GetDriverByName('GTiff') | ||
|
||
for i in self.eo_indices(): | ||
out_raster = driver.Create(out_file_path + f'_{i + 1}.tif', cols, rows, 1, gdal_type, | ||
options=['INTERLEAVE=BAND', 'COMPRESS=DEFLATE', f'PHOTOMETRIC={photometric}']) | ||
out_band = out_raster.GetRasterBand(1) | ||
out_data = self.__aligned_capture[:, :, i] | ||
out_data[out_data < 0] = 0 | ||
out_data[out_data > 2] = 2 # limit reflectance data to 200% to allow some specular reflections | ||
# if GDT_UInt16, scale reflectance images so 100% = 32768. GDT_UInt16 resolves to 2. | ||
out_band.WriteArray(out_data * 32768 if gdal_type == 2 else out_data) | ||
out_band.FlushCache() | ||
|
||
for i in self.lw_indices(): | ||
out_raster = driver.Create(out_file_path + f'_{i + 1}.tif', cols, rows, 1, gdal_type, | ||
options=['INTERLEAVE=BAND', 'COMPRESS=DEFLATE', f'PHOTOMETRIC={photometric}']) | ||
out_band = out_raster.GetRasterBand(1) | ||
# if GDT_UInt16, scale data from float degC to back to centi-Kelvin to fit into UInt16. | ||
out_data = (self.__aligned_capture[:, :, i] + 273.15) * 100 if gdal_type == 2 \ | ||
else self.__aligned_capture[:, :, i] | ||
if gdal_type == 2: | ||
out_data[out_data < 0] = 0 | ||
out_data[out_data > 65535] = 65535 | ||
out_band.WriteArray(out_data) | ||
out_band.FlushCache() | ||
|
||
def save_capture_as_rgb(self, out_file_name, gamma=1.4, downsample=1, white_balance='norm', hist_min_percent=0.5, | ||
hist_max_percent=99.5, sharpen=True, rgb_band_indices=(2, 1, 0)): | ||
""" | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replace 2 with
GDT_UInt16