Commit 2ff79eac authored by cponcele's avatar cponcele
Browse files

clean and comment Sonar widgets.py

parent 70812b04
......@@ -11,120 +11,164 @@ import netCDF4 as nc
class SingleDimensionSelector:
"""Single dimension widget, made of a slider and a check box"""
def __init__(self, name: str, value: int, max_value: int, enable: bool):
self.slider = IntSlider(description=name, value=value, min=0, max=max_value, disable=False)
self.checkbox = Checkbox(value=enable, description='enable')
self.slider = IntSlider(
description=name, value=value, min=0, max=max_value, disable=False
)
self.checkbox = Checkbox(value=enable, description="enable")
self.widget = HBox([self.slider, self.checkbox])
class DimensionsSelector:
"""Composite (VBox) storing all SingleDimensionSelector widget for one variable"""
def __init__(self):
self.widget = VBox([])
self.clear()
self.values = {}
self.values = {} # dictionnary use to remind latest dimension values
def clear(self, ):
def clear(
self,
):
"""Clear widget content"""
self.widget.children = ()
def add_child(self, name, max_value, current_value=None):
def add(self, name, max_value, current_value=None):
"""Add a new dimension and SingleDimensionSelector widget"""
enable = False
if current_value is None:
#try to retrieve a previous dimension with the same name (may be redefined in another group) and to reuse its value
current_value=0
# try to retrieve a previous dimension with the same name (may be redefined in another group) and to reuse its value
current_value = 0
if name in self.values:
#retrieve the previous widget with the same name
# retrieve the previous widget with the same name
previous_widget = self.values[name]
previous_value = previous_widget.slider.value
# reuse old values for the given dimension
if previous_value <= max_value:
current_value = previous_value
else:
current_value = max(max_value-1, 0)
current_value = max(max_value - 1, 0)
# reuse previous state
enable = previous_widget.checkbox.value
selector = SingleDimensionSelector(name=name, value=current_value, max_value=max_value, enable=enable)
# create widget
selector = SingleDimensionSelector(
name=name, value=current_value, max_value=max_value, enable=enable
)
# add it to the box container
self.widget.children += (selector.widget,)
# remember this widget for the dimension
self.values[name] = selector
def build_slice_index(self) -> str:
dict_index = {key: value.slider.value for (key, value) in self.values.items() if value.checkbox.value is True}
def build_slice_index(self) -> dict:
dict_index = {
key: value.slider.value
for (key, value) in self.values.items()
if value.checkbox.value is True
}
return dict_index
class AppLayout2:
def clear_output(self, event):
self.content_plt.clear_output()
"""Composite handling layout of all widgets"""
def clear_all(self):
"""clean up widgets"""
self.plt_button.disabled = True
self.plt_button.description = "plot selection"
self.clear_output(None)
self.content_text.value = "Empty content"
self.content_plt.clear_output()
def __init__(self, top=None):
self.top = top
self.content_text = Textarea(
"Empty content", layout={"width": "100%", "height": "100%"}
)
self.content_plt = Output(layout={"border": "1px solid black"})
# tree view widget
self.tree = Tree(multiple_selection=False, layout={"width": "50%"})
# Main widget
center = HBox([self.tree, self.content_text], layout={"width": "100%"})
# create widget for plot area
self.plt_button = Button(description="plot selection")
self.plt_button.disabled = True
clear_button = Button(description="clear")
clear_button.on_click(self.clear_output)
self.content_plt = Output(layout={"border": "1px solid black"})
clear_button.on_click(self.content_plt.clear_output())
plt_toolbox = HBox((self.plt_button, clear_button))
self.plt_dimension_selector = DimensionsSelector()
plt_widget = VBox([plt_toolbox, self.plt_dimension_selector.widget, self.content_plt])
plt_widget = VBox(
[plt_toolbox, self.plt_dimension_selector.widget, self.content_plt]
)
# put all widgets in a VBox
self.widget = VBox([top, center, plt_widget], layout={"width": "100%"})
def show(self):
"""Display widget"""
display(self.widget)
class NCExplorer:
def __init__(self, starting_path=r"C:\\"):
"""Create a notebook application allowing to load a netcdf file,
display in a tree view its groups and variables contents and inspect variables definitions and data
"""
def __init__(self, starting_path=r"C:\\"):
# create file chooser
self.fc = self._createFileChooser(starting_path, self.on_file_selected)
# create full widget
self.widget = AppLayout2(top=self.fc)
self.current_selection = None
# retain current selection
self.current_selection = None # nc.Variable
# handle plot event
self.widget.plt_button.on_click(self.plot_current_variable)
# if a path is given, initialize with it
if os.path.isfile(starting_path) and os.path.exists(starting_path):
self.on_file_selected(starting_path)
def on_file_selected(self, filename):
"""Handle a new file selection"""
self.current_file = filename
self.current_selection = None
print(f"{filename} was selected")
# clear previous selection
self.current_selection = None
# clear tree view
for n in self.widget.tree.nodes:
self.widget.tree.remove_node(n)
# clear link between node and datasets
self.node_dataset_dict = {}
# clear all widget
self.widget.clear_all()
# open file and set root dataset
self.current_reader = reader.NcReader(filename)
root = self.current_reader.dataset
# init first node
root_name = os.path.basename(filename)
root_node = Node(root_name, icon="file", icon_style="info", opened=False)
self.widget.tree.add_node(root_node)
print(f"{filename} was selected")
self.current_reader = reader.NcReader(filename)
root = self.current_reader.dataset
root_node.observe(self.handle_tree_click, "selected")
# create a dictonnary to retains informations between dataset and tree nodes
self.node_dataset_dict = dict()
self.node_dataset_dict[root_node] = root
root_node.observe(self.handle_tree_click, "selected")
# recurse all dataset and fill tree
self._recurseTree(current_group=root, current_node=root_node)
def show(self):
"""Display the widgets"""
return self.widget.show()
def _recurseTree(self, current_group: nc.Dataset, current_node: Node):
"""Recurse data set, and add subgroups/variables in the tree view node as parameter"""
for g in sorted(current_group.groups):
n = Node(g, icon="folder", icon_style="info", opened=False)
current_node.add_node(n)
......@@ -138,21 +182,17 @@ class NCExplorer:
self.node_dataset_dict[n] = variable
current_node.add_node(n)
def _get_variable_path_and_name(self, dataset: nc.Dataset):
if isinstance(dataset, nc.Variable):
vpath = "/"
parent = dataset._grp
if hasattr(parent, "path"):
vpath = parent.path
return (f"{vpath}/{dataset.name}", dataset.name)
return None, None
def print_node_overview(self, dataset):
def _display_node_overview(self, dataset):
"""
Compute dump text for the data and update widget
:param dataset:
:return:
"""
marker = "-> "
if isinstance(dataset, nc.Variable):
(full_path, name) = self._get_variable_path_and_name(dataset)
if self.current_reader._is_xsfvlen(dataset):
(full_path, name) = reader.NcReader.get_variable_path_and_name(dataset)
if reader.NcReader.is_variable_vlen(dataset):
desc = f"Dataset : {full_path} vlen({dataset.dtype})\n\n"
else:
desc = f"Dataset : {full_path} ({dataset.dtype})\n\n"
......@@ -200,24 +240,25 @@ class NCExplorer:
attributes = f"\nAttributes:\n"
v = "".join(
(f"\t{marker}{att}:{dataset.getncattr(att)}\n" for att in sorted(dataset.ncattrs()))
(
f"\t{marker}{att}:{dataset.getncattr(att)}\n"
for att in sorted(dataset.ncattrs())
)
)
desc = "".join([desc, attributes, v])
self.widget.content_text.value = str(desc)
def _update_current_selection(self, dataset):
"""Selected dataset changed"""
self.current_selection = dataset
self.print_node_overview(self.current_selection)
self._display_node_overview(self.current_selection)
if self.current_selection is not None and isinstance(
self.current_selection, nc.Variable
):
# update plot description
self.widget.plt_button.description = (
f"Plot {self.current_selection.name}"
)
self.widget.plt_button.description = f"Plot {self.current_selection.name}"
dimensions = self.current_selection.dimensions
shape = self.current_selection.shape
# shape could have one more dimension due to vlen
......@@ -225,22 +266,23 @@ class NCExplorer:
self.widget.plt_dimension_selector.clear()
for dim, s in zip(dimensions, shape):
self.widget.plt_dimension_selector.add_child(name=dim, max_value=s, current_value=None)
self.widget.plt_dimension_selector.add(
name=dim, max_value=s - 1, current_value=None
)
self.widget.plt_button.disabled = False
else:
self.widget.plt_button.disabled = True
def handle_tree_click(self, event):
if event["new"] and event["owner"] is not None:
node = event["owner"]
self._update_current_selection(self.node_dataset_dict[node])
def plot_ncvariable(self, dataset):
"""Plot a variable in widget content_plt context"""
with self.widget.content_plt:
path, name = self._get_variable_path_and_name(dataset)
path, name = reader.NcReader.get_variable_path_and_name(dataset)
if path is not None:
# build a slice index given all dimension selector
slice_index = self.widget.plt_dimension_selector.build_slice_index()
......@@ -250,13 +292,14 @@ class NCExplorer:
)
def plot_current_variable(self, event):
"""Retrieve current variable and plot it"""
if self.current_selection is not None and isinstance(
self.current_selection, nc.Variable
):
self.plot_ncvariable(self.current_selection)
# create a file Chooser Part
def _createFileChooser(self, path: str, callback):
"""Create file chooser"""
if not os.path.isfile(path):
fc = FileChooser(path)
else:
......@@ -272,6 +315,7 @@ class NCExplorer:
return fc
"""Code only used for debug purpose, should be started in a notebook"""
if __name__ == "__main__":
import matplotlib.pyplot as plt
......@@ -279,9 +323,11 @@ if __name__ == "__main__":
f = NCExplorer(d)
f.on_file_selected(d)
f.print_node_overview(f.current_reader.dataset["/Sonar/Beam_group1/backscatter_r"])
f._update_current_selection(f.current_reader.dataset["/Sonar/Beam_group1/backscatter_r"])
f.plot_ncvariable(
f.current_reader.dataset["/Sonar/Beam_group1/platform_longitude"]
f._display_node_overview(
f.current_reader.dataset["/Sonar/Beam_group1/backscatter_r"]
)
f._update_current_selection(
f.current_reader.dataset["/Sonar/Beam_group1/backscatter_r"]
)
f.plot_ncvariable(f.current_reader.dataset["/Sonar/Beam_group1/platform_longitude"])
plt.show()
......@@ -21,8 +21,10 @@
},
"outputs": [],
"source": [
"f=NCExplorer(starting_path=r'C:\\data\\datasets\\XSF\\data\\0006_20200504_111056_FG_EM122.xsf.nc')\n",
"f.show()"
"#create a NCExplorer with a default path\n",
"\n",
"explorer=NCExplorer(starting_path=r'C:\\data\\datasets\\XSF\\data\\0006_20200504_111056_FG_EM122.xsf.nc')\n",
"explorer.show()"
]
},
{
......
......@@ -85,14 +85,15 @@ class NcReader:
variable = self.dataset[variable_path]
if slice_index is None:
slice_index = dict()
if self._is_xsfvlen(variable):
if NcReader.is_variable_vlen(variable):
return self._get_vlen_variable(variable_path, slice_index)
return self._get_usual_variable(variable_path=variable_path,slice_index=slice_index)
def _get_variable(self, variable_path):
return self.dataset[variable_path]
def _is_xsfvlen(self, variable):
@staticmethod
def is_variable_vlen(variable):
"""
Check if the given variable is a variable length variable (in XSF variable length definition)
"""
......@@ -251,6 +252,21 @@ class NcReader:
header(f"Variable {variable_name} : {variable_path}")
pprint(f"{self.dataset[variable_path]}")
@staticmethod
def get_variable_path_and_name(dataset: nc.Dataset):
"""
Get a variable name and path, taking into account for Root group being a special dataset
:return a tuple containing path and name or (None,None) if not a variable
"""
if isinstance(dataset, nc.Variable):
vpath = "/"
parent = dataset._grp
if hasattr(parent, "path"):
vpath = parent.path
return (f"{vpath}/{dataset.name}", dataset.name)
return None, None
def _display_variable(
self,
variable_name,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment