ui module¶
This module contains the user interface for the hypercoast package.
SpectralWidget (HBox)
¶
A widget for spectral data visualization on a map.
Attributes:
Name | Type | Description |
---|---|---|
_host_map |
Map |
The map to host the widget. |
on_close |
function |
Function to be called when the widget is closed. |
_output_widget |
widgets.Output |
The output widget to display results. |
_output_control |
ipyleaflet.WidgetControl |
The control for the output widget. |
_on_map_interaction |
function |
Function to handle map interactions. |
_spectral_widget |
SpectralWidget |
The spectral widget itself. |
_spectral_control |
ipyleaflet.WidgetControl |
The control for the spectral widget. |
Source code in hypercoast/ui.py
class SpectralWidget(widgets.HBox):
"""
A widget for spectral data visualization on a map.
Attributes:
_host_map (Map): The map to host the widget.
on_close (function): Function to be called when the widget is closed.
_output_widget (widgets.Output): The output widget to display results.
_output_control (ipyleaflet.WidgetControl): The control for the output widget.
_on_map_interaction (function): Function to handle map interactions.
_spectral_widget (SpectralWidget): The spectral widget itself.
_spectral_control (ipyleaflet.WidgetControl): The control for the spectral widget.
"""
def __init__(
self, host_map, stack=True, position="topright", xlim=None, ylim=None, **kwargs
):
"""
Initializes a new instance of the SpectralWidget class.
Args:
host_map (Map): The map to host the widget.
stack (bool, optional): Whether to stack the plots. Defaults to True.
position (str, optional): The position of the widget on the map. Defaults to "topright".
xlim (tuple, optional): The x-axis limits. Defaults to None.
ylim (tuple, optional): The y-axis limits. Defaults to None.
"""
self._host_map = host_map
self.on_close = None
self._stack = stack
self._show_plot = False
fig_margin = {"top": 20, "bottom": 35, "left": 50, "right": 20}
fig = plt.figure(
# title=None,
fig_margin=fig_margin,
layout={"width": "500px", "height": "300px"},
)
self._fig = fig
self._host_map._fig = fig
layer_names = list(host_map.cog_layer_dict.keys())
layers_widget = widgets.Dropdown(options=layer_names)
layers_widget.layout.width = "18ex"
close_btn = widgets.Button(
icon="times",
tooltip="Close the widget",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
reset_btn = widgets.Button(
icon="trash",
tooltip="Remove all markers",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
settings_btn = widgets.Button(
icon="gear",
tooltip="Change layer settings",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
stack_btn = widgets.ToggleButton(
value=stack,
icon="area-chart",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
def settings_btn_click(_):
self._host_map._add_layer_editor(
position="topright",
layer_dict=self._host_map.cog_layer_dict[layers_widget.value],
)
settings_btn.on_click(settings_btn_click)
def reset_btn_click(_):
if hasattr(self._host_map, "_plot_marker_cluster"):
self._host_map._plot_marker_cluster.markers = []
self._host_map._plot_markers = []
if hasattr(self._host_map, "_spectral_data"):
self._host_map._spectral_data = {}
self._output_widget.clear_output()
self._show_plot = False
plt.clear()
reset_btn.on_click(reset_btn_click)
save_btn = widgets.Button(
icon="floppy-o",
tooltip="Save the data to a CSV",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
def chooser_callback(chooser):
if chooser.selected:
file_path = chooser.selected
self._host_map.spectral_to_csv(file_path)
if (
hasattr(self._host_map, "_file_chooser_control")
and self._host_map._file_chooser_control in self._host_map.controls
):
self._host_map.remove_control(self._host_map._file_chooser_control)
self._host_map._file_chooser.close()
def save_btn_click(_):
if not hasattr(self._host_map, "_spectral_data"):
return
self._output_widget.clear_output()
file_chooser = FileChooser(
os.getcwd(), layout=widgets.Layout(width="454px")
)
file_chooser.filter_pattern = "*.csv"
file_chooser.use_dir_icons = True
file_chooser.title = "Save spectral data to a CSV file"
file_chooser.default_filename = "spectral_data.csv"
file_chooser.show_hidden = False
file_chooser.register_callback(chooser_callback)
file_chooser_control = ipyleaflet.WidgetControl(
widget=file_chooser, position="topright"
)
self._host_map.add(file_chooser_control)
setattr(self._host_map, "_file_chooser", file_chooser)
setattr(self._host_map, "_file_chooser_control", file_chooser_control)
save_btn.on_click(save_btn_click)
def close_widget(_):
self.cleanup()
close_btn.on_click(close_widget)
super().__init__(
[layers_widget, settings_btn, stack_btn, reset_btn, save_btn, close_btn]
)
output = widgets.Output()
output_control = ipyleaflet.WidgetControl(widget=output, position="bottomright")
self._output_widget = output
self._output_control = output_control
self._host_map.add(output_control)
if not hasattr(self._host_map, "_spectral_data"):
self._host_map._spectral_data = {}
def handle_interaction(**kwargs):
latlon = kwargs.get("coordinates")
lat = latlon[0]
lon = latlon[1]
if kwargs.get("type") == "click" and self._host_map._layer_editor is None:
layer_name = layers_widget.value
if not hasattr(self._host_map, "_plot_markers"):
self._host_map._plot_markers = []
markers = self._host_map._plot_markers
marker_cluster = self._host_map._plot_marker_cluster
markers.append(ipyleaflet.Marker(location=latlon, draggable=False))
marker_cluster.markers = markers
self._host_map._plot_marker_cluster = marker_cluster
xlabel = "Wavelength (nm)"
ylabel = "Reflectance"
ds = self._host_map.cog_layer_dict[layer_name]["xds"]
if self._host_map.cog_layer_dict[layer_name]["hyper"] == "XARRAY":
da = extract_spectral(ds, lat, lon)
xlabel = "Band"
ylabel = "Value"
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "EMIT":
da = ds.sel(latitude=lat, longitude=lon, method="nearest")[
"reflectance"
]
if "wavelength" not in self._host_map._spectral_data:
self._host_map._spectral_data["wavelength"] = ds[
"wavelength"
].values
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "PACE":
try:
da = extract_pace(ds, lat, lon)
except:
da = xr.DataArray(
np.full(len(ds["wavelength"]), np.nan),
dims=["wavelength"],
coords={"wavelength": ds["wavelength"]},
)
if "wavelengths" not in self._host_map._spectral_data:
self._host_map._spectral_data["wavelengths"] = ds[
"wavelength"
].values
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "DESIS":
da = extract_desis(ds, lat, lon)
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "NEON":
da = extract_neon(ds, lat, lon)
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "AVIRIS":
da = extract_aviris(ds, lat, lon)
self._host_map._spectral_data[f"({lat:.4f} {lon:.4f})"] = da.values
if self._host_map.cog_layer_dict[layer_name]["hyper"] != "XARRAY":
da[da < 0] = np.nan
x_axis_options = {"label_offset": "30px"}
else:
x_axis_options = {
"label_offset": "30px",
"tick_format": "0d",
"num_ticks": da.sizes["band"],
}
axes_options = {
"x": x_axis_options,
"y": {"label_offset": "35px"},
}
if not stack_btn.value:
plt.clear()
plt.plot(
da.coords[da.dims[0]].values,
da.values,
axes_options=axes_options,
)
else:
color = np.random.rand(
3,
)
plt.plot(
da.coords[da.dims[0]].values,
da.values,
color=color,
axes_options=axes_options,
)
try:
if isinstance(self._fig.axes[0], bqplot.ColorAxis):
self._fig.axes = self._fig.axes[1:]
elif isinstance(self._fig.axes[-1], bqplot.ColorAxis):
self._fig.axes = self._fig.axes[:-1]
except Exception:
pass
plt.xlabel(xlabel)
plt.ylabel(ylabel)
if xlim:
plt.xlim(xlim[0], xlim[1])
if ylim:
plt.ylim(ylim[0], ylim[1])
if not self._show_plot:
with self._output_widget:
plt.show()
self._show_plot = True
self._host_map.default_style = {"cursor": "crosshair"}
self._host_map.on_interaction(handle_interaction)
self._on_map_interaction = handle_interaction
self._spectral_widget = self
self._spectral_control = ipyleaflet.WidgetControl(
widget=self, position=position
)
self._host_map.add(self._spectral_control)
def cleanup(self):
"""Removes the widget from the map and performs cleanup."""
if self._host_map:
self._host_map.default_style = {"cursor": "default"}
self._host_map.on_interaction(self._on_map_interaction, remove=True)
if self._output_control:
self._host_map.remove_control(self._output_control)
if self._output_widget:
self._output_widget.close()
self._output_widget = None
if self._spectral_control:
self._host_map.remove_control(self._spectral_control)
self._spectral_control = None
if self._spectral_widget:
self._spectral_widget.close()
self._spectral_widget = None
if hasattr(self._host_map, "_plot_marker_cluster"):
self._host_map._plot_marker_cluster.markers = []
self._host_map._plot_markers = []
if hasattr(self._host_map, "_spectral_data"):
self._host_map._spectral_data = {}
if hasattr(self, "_output_widget") and self._output_widget is not None:
self._output_widget.clear_output()
if self.on_close is not None:
self.on_close()
__init__(self, host_map, stack=True, position='topright', xlim=None, ylim=None, **kwargs)
special
¶
Initializes a new instance of the SpectralWidget class.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
host_map |
Map |
The map to host the widget. |
required |
stack |
bool |
Whether to stack the plots. Defaults to True. |
True |
position |
str |
The position of the widget on the map. Defaults to "topright". |
'topright' |
xlim |
tuple |
The x-axis limits. Defaults to None. |
None |
ylim |
tuple |
The y-axis limits. Defaults to None. |
None |
Source code in hypercoast/ui.py
def __init__(
self, host_map, stack=True, position="topright", xlim=None, ylim=None, **kwargs
):
"""
Initializes a new instance of the SpectralWidget class.
Args:
host_map (Map): The map to host the widget.
stack (bool, optional): Whether to stack the plots. Defaults to True.
position (str, optional): The position of the widget on the map. Defaults to "topright".
xlim (tuple, optional): The x-axis limits. Defaults to None.
ylim (tuple, optional): The y-axis limits. Defaults to None.
"""
self._host_map = host_map
self.on_close = None
self._stack = stack
self._show_plot = False
fig_margin = {"top": 20, "bottom": 35, "left": 50, "right": 20}
fig = plt.figure(
# title=None,
fig_margin=fig_margin,
layout={"width": "500px", "height": "300px"},
)
self._fig = fig
self._host_map._fig = fig
layer_names = list(host_map.cog_layer_dict.keys())
layers_widget = widgets.Dropdown(options=layer_names)
layers_widget.layout.width = "18ex"
close_btn = widgets.Button(
icon="times",
tooltip="Close the widget",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
reset_btn = widgets.Button(
icon="trash",
tooltip="Remove all markers",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
settings_btn = widgets.Button(
icon="gear",
tooltip="Change layer settings",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
stack_btn = widgets.ToggleButton(
value=stack,
icon="area-chart",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
def settings_btn_click(_):
self._host_map._add_layer_editor(
position="topright",
layer_dict=self._host_map.cog_layer_dict[layers_widget.value],
)
settings_btn.on_click(settings_btn_click)
def reset_btn_click(_):
if hasattr(self._host_map, "_plot_marker_cluster"):
self._host_map._plot_marker_cluster.markers = []
self._host_map._plot_markers = []
if hasattr(self._host_map, "_spectral_data"):
self._host_map._spectral_data = {}
self._output_widget.clear_output()
self._show_plot = False
plt.clear()
reset_btn.on_click(reset_btn_click)
save_btn = widgets.Button(
icon="floppy-o",
tooltip="Save the data to a CSV",
button_style="primary",
layout=widgets.Layout(width="32px"),
)
def chooser_callback(chooser):
if chooser.selected:
file_path = chooser.selected
self._host_map.spectral_to_csv(file_path)
if (
hasattr(self._host_map, "_file_chooser_control")
and self._host_map._file_chooser_control in self._host_map.controls
):
self._host_map.remove_control(self._host_map._file_chooser_control)
self._host_map._file_chooser.close()
def save_btn_click(_):
if not hasattr(self._host_map, "_spectral_data"):
return
self._output_widget.clear_output()
file_chooser = FileChooser(
os.getcwd(), layout=widgets.Layout(width="454px")
)
file_chooser.filter_pattern = "*.csv"
file_chooser.use_dir_icons = True
file_chooser.title = "Save spectral data to a CSV file"
file_chooser.default_filename = "spectral_data.csv"
file_chooser.show_hidden = False
file_chooser.register_callback(chooser_callback)
file_chooser_control = ipyleaflet.WidgetControl(
widget=file_chooser, position="topright"
)
self._host_map.add(file_chooser_control)
setattr(self._host_map, "_file_chooser", file_chooser)
setattr(self._host_map, "_file_chooser_control", file_chooser_control)
save_btn.on_click(save_btn_click)
def close_widget(_):
self.cleanup()
close_btn.on_click(close_widget)
super().__init__(
[layers_widget, settings_btn, stack_btn, reset_btn, save_btn, close_btn]
)
output = widgets.Output()
output_control = ipyleaflet.WidgetControl(widget=output, position="bottomright")
self._output_widget = output
self._output_control = output_control
self._host_map.add(output_control)
if not hasattr(self._host_map, "_spectral_data"):
self._host_map._spectral_data = {}
def handle_interaction(**kwargs):
latlon = kwargs.get("coordinates")
lat = latlon[0]
lon = latlon[1]
if kwargs.get("type") == "click" and self._host_map._layer_editor is None:
layer_name = layers_widget.value
if not hasattr(self._host_map, "_plot_markers"):
self._host_map._plot_markers = []
markers = self._host_map._plot_markers
marker_cluster = self._host_map._plot_marker_cluster
markers.append(ipyleaflet.Marker(location=latlon, draggable=False))
marker_cluster.markers = markers
self._host_map._plot_marker_cluster = marker_cluster
xlabel = "Wavelength (nm)"
ylabel = "Reflectance"
ds = self._host_map.cog_layer_dict[layer_name]["xds"]
if self._host_map.cog_layer_dict[layer_name]["hyper"] == "XARRAY":
da = extract_spectral(ds, lat, lon)
xlabel = "Band"
ylabel = "Value"
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "EMIT":
da = ds.sel(latitude=lat, longitude=lon, method="nearest")[
"reflectance"
]
if "wavelength" not in self._host_map._spectral_data:
self._host_map._spectral_data["wavelength"] = ds[
"wavelength"
].values
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "PACE":
try:
da = extract_pace(ds, lat, lon)
except:
da = xr.DataArray(
np.full(len(ds["wavelength"]), np.nan),
dims=["wavelength"],
coords={"wavelength": ds["wavelength"]},
)
if "wavelengths" not in self._host_map._spectral_data:
self._host_map._spectral_data["wavelengths"] = ds[
"wavelength"
].values
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "DESIS":
da = extract_desis(ds, lat, lon)
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "NEON":
da = extract_neon(ds, lat, lon)
elif self._host_map.cog_layer_dict[layer_name]["hyper"] == "AVIRIS":
da = extract_aviris(ds, lat, lon)
self._host_map._spectral_data[f"({lat:.4f} {lon:.4f})"] = da.values
if self._host_map.cog_layer_dict[layer_name]["hyper"] != "XARRAY":
da[da < 0] = np.nan
x_axis_options = {"label_offset": "30px"}
else:
x_axis_options = {
"label_offset": "30px",
"tick_format": "0d",
"num_ticks": da.sizes["band"],
}
axes_options = {
"x": x_axis_options,
"y": {"label_offset": "35px"},
}
if not stack_btn.value:
plt.clear()
plt.plot(
da.coords[da.dims[0]].values,
da.values,
axes_options=axes_options,
)
else:
color = np.random.rand(
3,
)
plt.plot(
da.coords[da.dims[0]].values,
da.values,
color=color,
axes_options=axes_options,
)
try:
if isinstance(self._fig.axes[0], bqplot.ColorAxis):
self._fig.axes = self._fig.axes[1:]
elif isinstance(self._fig.axes[-1], bqplot.ColorAxis):
self._fig.axes = self._fig.axes[:-1]
except Exception:
pass
plt.xlabel(xlabel)
plt.ylabel(ylabel)
if xlim:
plt.xlim(xlim[0], xlim[1])
if ylim:
plt.ylim(ylim[0], ylim[1])
if not self._show_plot:
with self._output_widget:
plt.show()
self._show_plot = True
self._host_map.default_style = {"cursor": "crosshair"}
self._host_map.on_interaction(handle_interaction)
self._on_map_interaction = handle_interaction
self._spectral_widget = self
self._spectral_control = ipyleaflet.WidgetControl(
widget=self, position=position
)
self._host_map.add(self._spectral_control)
cleanup(self)
¶
Removes the widget from the map and performs cleanup.
Source code in hypercoast/ui.py
def cleanup(self):
"""Removes the widget from the map and performs cleanup."""
if self._host_map:
self._host_map.default_style = {"cursor": "default"}
self._host_map.on_interaction(self._on_map_interaction, remove=True)
if self._output_control:
self._host_map.remove_control(self._output_control)
if self._output_widget:
self._output_widget.close()
self._output_widget = None
if self._spectral_control:
self._host_map.remove_control(self._spectral_control)
self._spectral_control = None
if self._spectral_widget:
self._spectral_widget.close()
self._spectral_widget = None
if hasattr(self._host_map, "_plot_marker_cluster"):
self._host_map._plot_marker_cluster.markers = []
self._host_map._plot_markers = []
if hasattr(self._host_map, "_spectral_data"):
self._host_map._spectral_data = {}
if hasattr(self, "_output_widget") and self._output_widget is not None:
self._output_widget.clear_output()
if self.on_close is not None:
self.on_close()