From 6fb5824b97f1c645053f2e946268f19bf9321eca Mon Sep 17 00:00:00 2001 From: tsutterley Date: Wed, 29 Nov 2023 15:18:28 -0800 Subject: [PATCH] feat: add widgets for making ATL08 PhoREAL requests --- clients/python/sliderule/ipysliderule.py | 220 +++++++++++++++++++++-- 1 file changed, 210 insertions(+), 10 deletions(-) diff --git a/clients/python/sliderule/ipysliderule.py b/clients/python/sliderule/ipysliderule.py index db635a11e..45dd38a79 100644 --- a/clients/python/sliderule/ipysliderule.py +++ b/clients/python/sliderule/ipysliderule.py @@ -319,6 +319,69 @@ def __init__(self, **kwargs): ) self.yapc_weight.layout.display = 'none' + # ATL08 PhoREAL parameters + # slider for setting PhoREAL histogram bin size + self.phoreal_binsize = ipywidgets.FloatSlider( + value=1, + min=0.25, + max=10, + step=0.25, + description='PhoREAL Bin Size:', + description_tooltip="PhoREAL Bin Size: size of the vertical photon bin in meters", + disabled=False, + continuous_update=False, + orientation='horizontal', + readout=True, + readout_format='0.2f', + style=self.style, + ) + + # dropdown menu for setting PhoREAL geolocation algorithm + # mean - takes the average value across all photons in the segment + # median - takes the median value across all photons in the segment + # center - takes the halfway value calculated by the average of the first and last photon in the segment + phoreal_geolocation_list = ['mean','median','center'] + self.phoreal_geolocation = ipywidgets.Dropdown( + options=phoreal_geolocation_list, + value='center', + description='PhoREAL Geolocation:', + description_tooltip=("PhoREAL Geolocation: method for calculating segment geolocation variables\n\t" + "mean: average value across all photons in the segment\n\t" + "median: median value across all photons in the segment\n\t" + "center: center of first and last photons in the segment"), + disabled=False, + style=self.style, + ) + + # checkbox for using PhoREAL absolute elevation + self.phoreal_abs_h = ipywidgets.Checkbox( + value=False, + description='PhoREAL use abs h', + description_tooltip=("PhoREAL use abs h: use absolute photon heights " + "instead of the normalized heights"), + disabled=False, + style=self.style, + ) + + # checkbox for using the PhoREAL ABoVE classifier + self.phoreal_above = ipywidgets.Checkbox( + value=False, + description='PhoREAL use ABoVE', + description_tooltip="PhoREAL use ABoVE: use the ABoVE photon classifier", + disabled=False, + style=self.style, + ) + + # checkbox for sending PhoREAL waveform + self.phoreal_waveform = ipywidgets.Checkbox( + value=False, + description='PhoREAL waveform', + description_tooltip=("PhoREAL waveform: send the photon height " + "histograms in addition to the vegetation statistics"), + disabled=False, + style=self.style, + ) + # slider for setting length of ATL06-SR segment in meters self.length = ipywidgets.IntSlider( value=40, @@ -449,7 +512,7 @@ def __init__(self, **kwargs): description='Projection:', description_tooltip=("Projection: leaflet map projection\n\t" "Global: Web Mercator (EPSG:3857)\n\t" - "Alaska Polar Stereographic (EPSG:5936)\n\t" + "North: Alaska Polar Stereographic (EPSG:5936)\n\t" "South: Polar Stereographic South (EPSG:3031)"), disabled=False, style=self.style, @@ -679,10 +742,11 @@ def set_atl03_defaults(self): """sets the default widget parameters for ATL03 requests """ # default photon classifications - class_options = ['atl03','atl08','yapc'] - self.classification.value = class_options + class_options = ['atl03', 'quality', 'atl08', 'yapc'] + self.classification.options = class_options + self.classification.value = ['atl03', 'atl08', 'yapc'] # default ATL03 confidence - self.confidence.value = -2 + self.confidence.value = -1 # set land class options land_options = [ 'atl08_noise', @@ -708,8 +772,9 @@ def set_atl06_defaults(self): """sets the default widget parameters for ATL06 requests """ # default photon classifications - class_options = ['atl03','atl08'] - self.classification.value = class_options + class_options = ['atl03', 'quality', 'atl08', 'yapc'] + self.classification.options = class_options + self.classification.value = ['atl03', 'atl08'] # default ATL06-SR confidence self.confidence.value = 4 # set land class options @@ -726,6 +791,40 @@ def set_atl06_defaults(self): self.file = copy.copy(self.atl06_filename) self.savelabel.value = self.file + def set_atl08_defaults(self): + """sets the default widget parameters for ATL08 requests + """ + # default photon classifications + class_options = ['atl03', 'quality', 'atl08'] + self.classification.options = class_options + self.classification.value = ['atl08'] + # default ATL08-SR confidence + self.confidence.value = -1 + # set land class options + land_options = [ + 'atl08_ground', + 'atl08_canopy', + 'atl08_top_of_canopy', + ] + self.land_class.value = land_options + # set default ATL08-SR length + self.length.value = 30 + # set PhoREAL parameters + self.phoreal_binsize.value = 1.0 + self.phoreal_geolocation.value = 'center' + self.phoreal_abs_h.value = False + self.phoreal_above.value = False + self.phoreal_waveform.value = False + # update variable list for ATL08-SR variables + variable_list = ['h_canopy', 'h_min_canopy', 'h_mean_canopy', + 'h_max_canopy', 'canopy_openness', 'h_te_median', + 'landcover', 'snowcover', 'solar_elevation', 'cycle', 'rgt'] + self.variable.options = variable_list + self.variable.value = 'h_canopy' + # set default filename + self.file = copy.copy(self.atl08_filename) + self.savelabel.value = self.file + def atl03(self, **kwargs): """returns a list of widgets for SlideRule ATL03 requests """ @@ -783,6 +882,33 @@ def atl06(self, **kwargs): self.sigma, ] + def atl08(self, **kwargs): + """returns a list of widgets for SlideRule ATL08 requests + """ + kwargs.setdefault('display', 'advanced') + assert str(kwargs['display']).lower() in ['advanced','basic'] + if (str(kwargs['display']).lower() == 'basic'): + return [ + self.surface_type, + self.length, + self.phoreal_binsize, + ] + else: + return [ + self.classification, + self.surface_type, + self.confidence, + self.quality, + self.land_class, + self.phoreal_binsize, + self.phoreal_geolocation, + self.phoreal_abs_h, + self.phoreal_above, + self.phoreal_waveform, + self.length, + self.step, + ] + @property def time_start(self): """start time in ISO format @@ -916,7 +1042,7 @@ def set_loadfile(self, sender): @property def atl03_filename(self): - """default input and output file string + """default input and output file string for ATL03 requests """ # get sliderule submission time now = datetime.datetime.now().strftime('%Y%m%d%H%M%S') @@ -925,13 +1051,22 @@ def atl03_filename(self): @property def atl06_filename(self): - """default input and output file string + """default input and output file string for ATL06 requests """ # get sliderule submission time now = datetime.datetime.now().strftime('%Y%m%d%H%M%S') args = (now, self.release.value) return "ATL06-SR_{0}_{1}.h5".format(*args) + @property + def atl08_filename(self): + """default input and output file string for ATL08 requests + """ + # get sliderule submission time + now = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + args = (now, self.release.value) + return "ATL08-SR_{0}_{1}.h5".format(*args) + @property def format(self): """return the file format from file string @@ -1074,6 +1209,46 @@ def build_atl06(self, **parms): # return the parameter dictionary return parms + # build sliderule ATL08 parameters using latest values from widget + def build_atl08(self, **parms): + """Build a SlideRule parameters dictionary for making ATL08 requests + + Parameters + ---------- + parms : dict, dictionary of SlideRule parameters to update + """ + # default parameters for all cases + # still return photon segments that fail checks + parms["pass_invalid"] = True + # length of ATL06-SR segment in meters + parms["len"] = self.length.value + # step distance for successive ATL06-SR segments in meters + parms["res"] = self.step.value + # PhoREAL parameters + parms["phoreal"] = {} + parms["phoreal"]["binsize"] = self.phoreal_binsize.value + parms["phoreal"]["geoloc"] = self.phoreal_geolocation.value + parms["phoreal"]["use_abs_h"] = self.phoreal_abs_h.value + parms["phoreal"]["send_waveform"] = self.phoreal_waveform.value + parms["phoreal"]["above_classifier"] = self.phoreal_above.value + # photon classification + # atl03 photon confidence level + if ('atl03' in self.classification.value): + # surface type: 0-land, 1-ocean, 2-sea ice, 3-land ice, 4-inland water + parms["srt"] = self.surface_type.index + # confidence level for PE selection + parms["cnf"] = self.confidence.value + # atl03 photon quality flags + if ('quality' in self.classification.value): + # confidence level for PE selection + parms["quality_ph"] = list(self.quality.value) + # atl08 land classification flags + if ('atl08' in self.classification.value): + # ATL08 land surface classifications + parms["atl08_class"] = list(self.land_class.value) + # return the parameter dictionary + return parms + # update values from widget using sliderule parameters dictionary def set_values(self, parms): """Set widget values using a SlideRule parameters dictionary @@ -1131,6 +1306,17 @@ def set_values(self, parms): self.yapc_win_h.value = parms["yapc"]["win_h"] if ('yapc' in parms.keys()) and ('win_x' in parms['yapc'].keys()): self.yapc_win_x.value = parms["yapc"]["win_x"] + # PhoREAL parameters + if ('phoreal' in parms.keys()) and ('binsize' in parms['phoreal'].keys()): + self.phoreal_binsize.value = parms["phoreal"]["binsize"] + if ('phoreal' in parms.keys()) and ('geoloc' in parms['phoreal'].keys()): + self.phoreal_geolocation.value = parms["phoreal"]["geoloc"] + if ('phoreal' in parms.keys()) and ('use_abs_h' in parms['phoreal'].keys()): + self.phoreal_abs_h.value = parms["yapc"]["use_abs_h"] + if ('phoreal' in parms.keys()) and ('send_waveform' in parms['phoreal'].keys()): + self.phoreal_waveform.value = parms["phoreal"]["send_waveform"] + if ('phoreal' in parms.keys()) and ('above_classifier' in parms['phoreal'].keys()): + self.phoreal_above.value = parms["phoreal"]["above_classifier"] # update values return self @@ -2205,6 +2391,14 @@ def default_atl06_fields(): return ['cycle', 'dh_fit_dx', 'gt', 'h_mean', 'h_sigma', 'rgt', 'rms_misfit', 'w_surface_window_final'] + @staticmethod + def default_atl08_fields(): + """List of ATL08-SR tooltip fields + """ + return ['canopy_openness', 'cycle', 'gt', 'h_canopy', + 'h_min_canopy', 'h_mean_canopy', + 'h_max_canopy', 'h_te_median', 'rgt'] + @staticmethod def default_mosaic_fields(**kwargs): kwargs.setdefault('with_flags', False) @@ -2233,6 +2427,8 @@ def __init__(self, gdf): # initialize data and colorbars self.geojson = None self.tooltip = None + self.tooltip_width = None + self.tooltip_height = None self.fields = [] self.colorbar = None # initialize hover control @@ -2273,6 +2469,8 @@ def GeoData(self, m, **kwargs): kwargs.setdefault('stride', None) kwargs.setdefault('max_plot_points', 10000) kwargs.setdefault('tooltip', True) + kwargs.setdefault('tooltip_height', "300px") + kwargs.setdefault('tooltip_width', "220px") kwargs.setdefault('fields', []) kwargs.setdefault('colorbar', True) kwargs.setdefault('position', 'topright') @@ -2331,6 +2529,8 @@ def GeoData(self, m, **kwargs): self.tooltip = ipywidgets.HTML() self.tooltip.layout.margin = "0px 20px 20px 20px" self.tooltip.layout.visibility = 'hidden' + self.tooltip_height = kwargs['tooltip_height'] + self.tooltip_width = kwargs['tooltip_width'] # create widget for hover tooltips self.hover_control = ipyleaflet.WidgetControl( widget=self.tooltip, @@ -2361,8 +2561,8 @@ def handle_hover(self, feature, **kwargs): self.tooltip.value = '{0}: {1}
'.format('id',feature['id']) self.tooltip.value += '
'.join(['{0}: {1}'.format(field, feature["properties"][field]) for field in self.fields]) - self.tooltip.layout.width = "220px" - self.tooltip.layout.height = "300px" + self.tooltip.layout.width = self.tooltip_width + self.tooltip.layout.height = self.tooltip_height self.tooltip.layout.visibility = 'visible' self.map.add(self.hover_control)