xarray-graph 0.2.1__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xarray-graph
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: PyQt/PySide UI for graphing (x,y) slices of Xarray datasets.
5
5
  Keywords: PyQt,PySide,xarray,graph
6
6
  Home-page: https://github.com/marcel-goldschen-ohm/xarray-graph
@@ -22,9 +22,9 @@ Requires-Dist: numpy>=1.26.2
22
22
  Requires-Dist: xarray>=2023.12.0
23
23
  Requires-Dist: qtpy>=2.4.1
24
24
  Requires-Dist: qtawesome>=1.3.0
25
- Requires-Dist: pyqt-ext>=1.2.0
25
+ Requires-Dist: pyqt-ext>=1.2.1
26
26
  Requires-Dist: pyqtgraph-ext>=1.2.0
27
- Requires-Dist: xarray-treeview>=1.2.0
27
+ Requires-Dist: xarray-treeview>=1.2.2
28
28
  Requires-Dist: scipy>=1.11.4
29
29
  Requires-Dist: lmfit>=1.2.2
30
30
  Description-Content-Type: text/markdown
@@ -11,9 +11,9 @@ dependencies = [
11
11
  "xarray>=2023.12.0",
12
12
  "qtpy>=2.4.1",
13
13
  "qtawesome>=1.3.0",
14
- "pyqt-ext>=1.2.0",
14
+ "pyqt-ext>=1.2.1",
15
15
  "pyqtgraph-ext>=1.2.0",
16
- "xarray-treeview>=1.2.0",
16
+ "xarray-treeview>=1.2.2",
17
17
  "scipy>=1.11.4",
18
18
  "lmfit>=1.2.2",
19
19
  ]
@@ -35,7 +35,7 @@ classifiers = [
35
35
  "Programming Language :: Python :: 3.12",
36
36
  "Programming Language :: Python :: 3.13",
37
37
  ]
38
- version = "0.2.1"
38
+ version = "0.2.3"
39
39
 
40
40
  [project.license]
41
41
  text = "MIT"
@@ -42,11 +42,6 @@ from xarray_treeview import *
42
42
  # version info (stored in metadata in case needed later)
43
43
  from importlib.metadata import version
44
44
  XARRAY_GRAPH_VERSION = version('xarray-graph')
45
- try:
46
- i = re.search(r'[a-zA-Z]', XARRAY_GRAPH_VERSION).start()
47
- XARRAY_GRAPH_VERSION = XARRAY_GRAPH_VERSION[:i].rstrip('.')
48
- except Exception:
49
- pass
50
45
 
51
46
 
52
47
  # Currently, color is handled by the widgets themselves.
@@ -120,16 +115,16 @@ class XarrayGraph(QMainWindow):
120
115
  if data is None:
121
116
  data = DataTree()
122
117
  elif isinstance(data, xr.Dataset):
123
- data = DataTree(ds=data)
118
+ data = DataTree(data=data)
124
119
  elif isinstance(data, xr.DataArray):
125
- data = DataTree(ds=xr.Dataset(data_vars={data.name: data}))
120
+ data = DataTree(data=xr.Dataset(data_vars={data.name: data}))
126
121
  elif isinstance(data, np.ndarray):
127
- data = DataTree(ds=xr.Dataset(data_vars={'data': data}))
122
+ data = DataTree(data=xr.Dataset(data_vars={'data': data}))
128
123
  else:
129
124
  # assume list or tuple of two np.ndarrays (x, y)
130
125
  try:
131
126
  x, y = data
132
- data = DataTree(ds=xr.Dataset(data_vars={'y': ('x', y)}, coords={'x': ('x', x)}))
127
+ data = DataTree(data=xr.Dataset(data_vars={'y': ('x', y)}, coords={'x': ('x', x)}))
133
128
  except Exception:
134
129
  raise ValueError('XarrayGraph.data.setter: Invalid input.')
135
130
 
@@ -151,6 +146,12 @@ class XarrayGraph(QMainWindow):
151
146
  # metadata
152
147
  self.attrs['xarray-graph-version'] = XARRAY_GRAPH_VERSION
153
148
 
149
+ # regions tree view
150
+ self._region_treeview.model().setRoot(AxisRegionTreeItem(self.regions))
151
+
152
+ # notes
153
+ self._notes_edit.setPlainText(self.attrs.get('notes', ''))
154
+
154
155
  # populate array math selections
155
156
  # self._update_array_math_comboboxes()
156
157
 
@@ -277,7 +278,7 @@ class XarrayGraph(QMainWindow):
277
278
  filepath = QFileDialog.getExistingDirectory(self, 'Open from Xarray data store...')
278
279
  if filepath == '':
279
280
  return None
280
- self.data = open_datatree(filepath)
281
+ self.data = open_datatree(filepath, 'zarr')
281
282
  self._filepath = filepath
282
283
  self.setWindowTitle(os.path.split(filepath)[1])
283
284
 
@@ -297,31 +298,20 @@ class XarrayGraph(QMainWindow):
297
298
  self.setWindowTitle(os.path.split(filepath)[1])
298
299
 
299
300
  def import_data(self, filepath: str = '', filetype: str = '') -> None:
300
- # ds: xr.Dataset | None = None
301
- # if filetype == 'pCLAMP':
302
- # # TODO: implement
303
- # QMessageBox.warning(self, 'Import pCLAMP', 'Importing pCLAMP files is not yet implemented.')
304
- # return
305
- # elif filetype == 'HEKA':
306
- # # TODO: implement
307
- # QMessageBox.warning(self, 'Import HEKA', 'Importing HEKA files is not yet implemented.')
308
- # return
309
- # elif filetype == 'GOLab TEVC':
310
- # ds, filepath = import_golab_tevc(filepath)
311
- # if ds is None:
312
- # return
313
- # self.set_data(ds)
314
- # if 'regions' in ds.attrs:
315
- # self.metadata['regions'] = ds.attrs['regions']
316
- # del ds.attrs['regions']
317
- # region_labels = [region['label'] for region in self.metadata['regions']]
318
- # self._region_label_list.clear()
319
- # self._region_label_list.addItems(region_labels)
320
- # self.set_selected_region_labels(region_labels)
321
- # if 'notes' in ds.attrs:
322
- # self.metadata['notes'] = ds.attrs['notes']
323
- # del ds.attrs['notes']
324
- # self.load_notes(self.metadata['notes'])
301
+ ds: xr.Dataset | None = None
302
+ if filetype == 'pCLAMP':
303
+ # TODO: implement
304
+ QMessageBox.warning(self, 'Import pCLAMP', 'Importing pCLAMP files is not yet implemented.')
305
+ return
306
+ elif filetype == 'HEKA':
307
+ # TODO: implement
308
+ QMessageBox.warning(self, 'Import HEKA', 'Importing HEKA files is not yet implemented.')
309
+ return
310
+ elif filetype == 'GOLab TEVC':
311
+ ds, filepath = import_golab_tevc(filepath)
312
+ if ds is None:
313
+ return
314
+ self.data = ds
325
315
  self._filepath = os.path.splitext(filepath)[0]
326
316
  self.setWindowTitle(os.path.split(filepath)[1])
327
317
 
@@ -915,8 +905,9 @@ class XarrayGraph(QMainWindow):
915
905
  item.deleteLater()
916
906
  self.add_region(region)
917
907
 
918
- # stop drawing regions (draw one at a time)
919
- self._set_region_drawing_mode(False)
908
+ # stop drawing regions (draw one at a time)?
909
+ if self._draw_single_region_action.isChecked():
910
+ self._set_region_drawing_mode(False)
920
911
 
921
912
  def _setup_ui(self) -> None:
922
913
  self._setup_menubar()
@@ -926,11 +917,13 @@ class XarrayGraph(QMainWindow):
926
917
  self._control_panel_toolbar.setStyleSheet("QToolBar{spacing:2px;}")
927
918
  self._control_panel_toolbar.setIconSize(QSize(DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE))
928
919
  self._control_panel_toolbar.setMovable(False)
920
+ self._control_panel_toolbar.setContextMenuPolicy(Qt.ContextMenuPolicy.PreventContextMenu)
929
921
 
930
922
  self._plot_grid_toolbar = QToolBar()
931
923
  self._plot_grid_toolbar.setStyleSheet("QToolBar{spacing:2px;}")
932
924
  self._plot_grid_toolbar.setIconSize(QSize(DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE))
933
925
  self._plot_grid_toolbar.setMovable(False)
926
+ self._plot_grid_toolbar.setContextMenuPolicy(Qt.ContextMenuPolicy.PreventContextMenu)
934
927
  icon_button = QToolButton()
935
928
  icon_button.setIcon(qta.icon('fa5s.cubes', options=[{'opacity': 0.5}]))
936
929
  icon_button.pressed.connect(self.refresh)
@@ -1001,6 +994,18 @@ class XarrayGraph(QMainWindow):
1001
994
  self._region_button.clicked.connect(self._set_region_drawing_mode)
1002
995
  self._action_after_dim_iter_things = self._plot_grid_toolbar.addWidget(self._region_button)
1003
996
 
997
+ self._region_button_menu = QMenu()
998
+ self._draw_single_region_action = QAction('Draw single region', self._region_button_menu, checkable=True, checked=True)
999
+ self._draw_multiple_regions_action = QAction('Draw multiple regions', self._region_button_menu, checkable=True, checked=False)
1000
+ self._region_button_menu.addAction(self._draw_single_region_action)
1001
+ self._region_button_menu.addAction(self._draw_multiple_regions_action)
1002
+ group = QActionGroup(self._region_button_menu)
1003
+ group.addAction(self._draw_single_region_action)
1004
+ group.addAction(self._draw_multiple_regions_action)
1005
+ group.setExclusive(True)
1006
+ self._region_button.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
1007
+ self._region_button.customContextMenuRequested.connect(lambda pos: self._region_button_menu.exec_(self._region_button.mapToGlobal(pos)))
1008
+
1004
1009
  self._home_button = QToolButton()
1005
1010
  self._home_button.setIcon(qta.icon('mdi.home-outline', options=[{'opacity': 0.5}]))
1006
1011
  self._home_button.setToolTip('Autoscale all plots')
@@ -1054,7 +1059,8 @@ class XarrayGraph(QMainWindow):
1054
1059
  button.released.connect(lambda i=self._control_panel.count(): self._toggle_control_panel_at(i))
1055
1060
  self._control_panel_toolbar.addWidget(button)
1056
1061
 
1057
- self._data_treeview = XarrayTreeView()
1062
+ self._data_treeviewer = XarrayTreeViewer()
1063
+ self._data_treeview = self._data_treeviewer.view()
1058
1064
  self._data_treeview.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
1059
1065
  root: XarrayTreeItem = XarrayTreeItem(node=self.data, key=None)
1060
1066
  model: XarrayTreeModel = XarrayTreeModel(root)
@@ -1062,6 +1068,7 @@ class XarrayGraph(QMainWindow):
1062
1068
  self._data_treeview.setShowCoords(False)
1063
1069
  self._data_treeview.setModel(model)
1064
1070
  self._data_treeview.selectionWasChanged.connect(self._on_tree_selection_changed)
1071
+ self._data_treeviewer.setSizes([100, 1])
1065
1072
 
1066
1073
  self._xdim_combobox = QComboBox()
1067
1074
  self._xdim_combobox.currentTextChanged.connect(self.set_xdim)
@@ -1071,7 +1078,7 @@ class XarrayGraph(QMainWindow):
1071
1078
  vbox.setContentsMargins(0, 0, 0, 0)
1072
1079
  vbox.setSpacing(0)
1073
1080
 
1074
- vbox.addWidget(self._data_treeview)
1081
+ vbox.addWidget(self._data_treeviewer)
1075
1082
 
1076
1083
  form = QFormLayout()
1077
1084
  form.setContentsMargins(5, 3, 0, 3)
@@ -1505,6 +1512,13 @@ class XarrayGraph(QMainWindow):
1505
1512
 
1506
1513
  self._control_panel.addWidget(scroll_area)
1507
1514
 
1515
+ # def _show_region_context_menu(self, pos: QPoint) -> None:
1516
+ # if not hasattr(self, '_region_button_menu'):
1517
+ # self._region_button_menu = QMenu()
1518
+ # menu = QMenu()
1519
+ # menu.addAction('Draw X-axis regions until unchecked', self.clear_regions)
1520
+ # menu.exec(self.sender().mapToGlobal(pos))
1521
+
1508
1522
  def _set_region_drawing_mode(self, draw: bool | None = None) -> None:
1509
1523
  if draw is None:
1510
1524
  draw = self._region_button.isChecked()
@@ -1733,7 +1747,7 @@ class XarrayGraph(QMainWindow):
1733
1747
  i = np.argpartition(x, len(x) // 2)[len(x) // 2]
1734
1748
  return x[i]
1735
1749
  if measure_type in ['Min', 'Max', 'AbsMax', 'Peaks']:
1736
- peak_width = self._peak_width_spinbox.value()
1750
+ peak_width = self._peak_half_width_spinbox.value()
1737
1751
  if peak_width > 0:
1738
1752
  def get_peak_index_range(mask, center_index):
1739
1753
  start, stop = center_index, center_index + 1
@@ -2018,21 +2032,21 @@ class XarrayGraph(QMainWindow):
2018
2032
  dims = yarr.dims
2019
2033
 
2020
2034
  # region mask for fit optimization and/or evaluation
2021
- if regions and (self._curve_fit_optimize_in_visible_regions_checkbox.isChecked() or self._curve_fit_evaluate_in_visible_regions_checkbox.isChecked()):
2035
+ if regions and (self._curve_fit_optimize_in_regions_checkbox.isChecked() or self._curve_fit_evaluate_in_regions_checkbox.isChecked()):
2022
2036
  # mask for combined regions
2023
2037
  regions_mask = np.full(xdata.shape, False)
2024
2038
  for region in regions:
2025
2039
  xmin, xmax = region
2026
2040
  regions_mask[(xdata >= xmin) & (xdata <= xmax)] = True
2027
2041
 
2028
- if regions and self._curve_fit_optimize_in_visible_regions_checkbox.isChecked():
2042
+ if regions and self._curve_fit_optimize_in_regions_checkbox.isChecked():
2029
2043
  xinput = xdata[regions_mask]
2030
2044
  yinput = ydata[regions_mask]
2031
2045
  else:
2032
2046
  xinput = xdata
2033
2047
  yinput = ydata
2034
2048
 
2035
- if regions and self._curve_fit_evaluate_in_visible_regions_checkbox.isChecked():
2049
+ if regions and self._curve_fit_evaluate_in_regions_checkbox.isChecked():
2036
2050
  xoutput = xdata[regions_mask]
2037
2051
  else:
2038
2052
  xoutput = xdata
@@ -2207,43 +2221,41 @@ def permutations(coords: dict) -> list[dict]:
2207
2221
  return permutations
2208
2222
 
2209
2223
 
2210
- # def import_golab_tevc(filepath: str = '', parent: QWidget = None) -> tuple[xr.Dataset, str]:
2211
- # if filepath == '':
2212
- # filepath, _filter = QFileDialog.getOpenFileName(parent, 'Import GoLab TEVC', '', 'GoLab TEVC (*.mat)')
2213
- # if filepath == '':
2214
- # return None
2215
- # matdict = sp.io.loadmat(filepath, simplify_cells=True)
2216
- # # print(matdict)
2217
- # current = matdict['current']
2218
- # current_units = matdict['current_units']
2219
- # if len(current_units) > 1:
2220
- # prefix = current_units[0]
2221
- # if prefix in metric_scale_factors:
2222
- # current *= metric_scale_factors[prefix]
2223
- # current_units = current_units[1:]
2224
- # time = np.arange(len(current)) * matdict['time_interval_sec']
2225
- # ds = xr.Dataset(
2226
- # data_vars={
2227
- # 'current': (['time'], current, {'units': current_units}),
2228
- # },
2229
- # coords={
2230
- # 'time': (['time'], time, {'units': 's'}),
2231
- # },
2232
- # )
2233
- # if 'events' in matdict and matdict['events']:
2234
- # ds.attrs['regions'] = []
2235
- # for event in matdict['events']:
2236
- # time = event['time_sec']
2237
- # text = event['text']
2238
- # ds.attrs['regions'].append({
2239
- # 'dim': 'time',
2240
- # 'label': f'{time:.6f}',
2241
- # 'region': [time, time],
2242
- # 'text': text,
2243
- # })
2244
- # if 'notes' in matdict:
2245
- # ds.attrs['notes'] = matdict['notes']
2246
- # return ds, filepath
2224
+ def import_golab_tevc(filepath: str = '', parent: QWidget = None) -> tuple[xr.Dataset, str]:
2225
+ if filepath == '':
2226
+ filepath, _filter = QFileDialog.getOpenFileName(parent, 'Import GoLab TEVC', '', 'GoLab TEVC (*.mat)')
2227
+ if filepath == '':
2228
+ return None
2229
+ matdict = sp.io.loadmat(filepath, simplify_cells=True)
2230
+ # print(matdict)
2231
+ current = matdict['current']
2232
+ current_units = matdict['current_units']
2233
+ if len(current_units) > 1:
2234
+ prefix = current_units[0]
2235
+ if prefix in metric_scale_factors:
2236
+ current *= metric_scale_factors[prefix]
2237
+ current_units = current_units[1:]
2238
+ time = np.arange(len(current)) * matdict['time_interval_sec']
2239
+ ds = xr.Dataset(
2240
+ data_vars={
2241
+ 'current': (['time'], current, {'units': current_units}),
2242
+ },
2243
+ coords={
2244
+ 'time': (['time'], time, {'units': 's'}),
2245
+ },
2246
+ )
2247
+ if 'events' in matdict and matdict['events']:
2248
+ ds.attrs['regions'] = []
2249
+ for event in matdict['events']:
2250
+ time = event['time_sec']
2251
+ text = event['text']
2252
+ ds.attrs['regions'].append({
2253
+ 'region': {'time': [time, time]},
2254
+ 'text': text,
2255
+ })
2256
+ if 'notes' in matdict:
2257
+ ds.attrs['notes'] = matdict['notes']
2258
+ return ds, filepath
2247
2259
 
2248
2260
 
2249
2261
  def test_live():
File without changes
File without changes