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.
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/PKG-INFO +3 -3
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/pyproject.toml +3 -3
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/src/xarray_graph/XarrayGraph.py +92 -80
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/LICENSE +0 -0
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/README.md +0 -0
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/src/xarray_graph/__init__.py +0 -0
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/src/xarray_graph/__main__.py +0 -0
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/src/xarray_graph/tmp/RegionsManager.py +0 -0
- {xarray_graph-0.2.1 → xarray_graph-0.2.3}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: xarray-graph
|
|
3
|
-
Version: 0.2.
|
|
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.
|
|
25
|
+
Requires-Dist: pyqt-ext>=1.2.1
|
|
26
26
|
Requires-Dist: pyqtgraph-ext>=1.2.0
|
|
27
|
-
Requires-Dist: xarray-treeview>=1.2.
|
|
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.
|
|
14
|
+
"pyqt-ext>=1.2.1",
|
|
15
15
|
"pyqtgraph-ext>=1.2.0",
|
|
16
|
-
"xarray-treeview>=1.2.
|
|
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.
|
|
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(
|
|
118
|
+
data = DataTree(data=data)
|
|
124
119
|
elif isinstance(data, xr.DataArray):
|
|
125
|
-
data = DataTree(
|
|
120
|
+
data = DataTree(data=xr.Dataset(data_vars={data.name: data}))
|
|
126
121
|
elif isinstance(data, np.ndarray):
|
|
127
|
-
data = DataTree(
|
|
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(
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
#
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|