xarray-graph 0.2.0__tar.gz → 0.2.2__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.0 → xarray_graph-0.2.2}/PKG-INFO +9 -4
- {xarray_graph-0.2.0 → xarray_graph-0.2.2}/README.md +5 -0
- {xarray_graph-0.2.0 → xarray_graph-0.2.2}/pyproject.toml +5 -5
- {xarray_graph-0.2.0 → xarray_graph-0.2.2}/src/xarray_graph/XarrayGraph.py +75 -74
- xarray_graph-0.2.2/src/xarray_graph/__main__.py +76 -0
- xarray_graph-0.2.0/src/xarray_graph/__main__.py +0 -14
- {xarray_graph-0.2.0 → xarray_graph-0.2.2}/LICENSE +0 -0
- {xarray_graph-0.2.0 → xarray_graph-0.2.2}/src/xarray_graph/__init__.py +0 -0
- {xarray_graph-0.2.0/src/xarray_graph → xarray_graph-0.2.2/src/xarray_graph/tmp}/RegionsManager.py +0 -0
- {xarray_graph-0.2.0 → xarray_graph-0.2.2}/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.2
|
|
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
|
|
@@ -20,11 +20,11 @@ Project-URL: Repository, https://github.com/marcel-goldschen-ohm/xarray-graph
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Requires-Dist: numpy>=1.26.2
|
|
22
22
|
Requires-Dist: xarray>=2023.12.0
|
|
23
|
-
Requires-Dist: xarray-tree>=0.1.0
|
|
24
23
|
Requires-Dist: qtpy>=2.4.1
|
|
25
24
|
Requires-Dist: qtawesome>=1.3.0
|
|
26
|
-
Requires-Dist: pyqt-ext>=1.1
|
|
27
|
-
Requires-Dist: pyqtgraph-ext>=1.
|
|
25
|
+
Requires-Dist: pyqt-ext>=1.2.1
|
|
26
|
+
Requires-Dist: pyqtgraph-ext>=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
|
|
@@ -38,6 +38,11 @@ Should work with PySide6, PyQt6, or PyQt5.
|
|
|
38
38
|
pip install PySide6 xarray-graph
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
# Run
|
|
42
|
+
```shell
|
|
43
|
+
xarray-graph
|
|
44
|
+
```
|
|
45
|
+
|
|
41
46
|
# TODO
|
|
42
47
|
- i/o
|
|
43
48
|
- tests
|
|
@@ -9,11 +9,11 @@ requires-python = ">=3.9"
|
|
|
9
9
|
dependencies = [
|
|
10
10
|
"numpy>=1.26.2",
|
|
11
11
|
"xarray>=2023.12.0",
|
|
12
|
-
"xarray-tree>=0.1.0",
|
|
13
12
|
"qtpy>=2.4.1",
|
|
14
13
|
"qtawesome>=1.3.0",
|
|
15
|
-
"pyqt-ext>=1.1
|
|
16
|
-
"pyqtgraph-ext>=1.
|
|
14
|
+
"pyqt-ext>=1.2.1",
|
|
15
|
+
"pyqtgraph-ext>=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.
|
|
38
|
+
version = "0.2.2"
|
|
39
39
|
|
|
40
40
|
[project.license]
|
|
41
41
|
text = "MIT"
|
|
@@ -45,7 +45,7 @@ homepage = "https://github.com/marcel-goldschen-ohm/xarray-graph"
|
|
|
45
45
|
repository = "https://github.com/marcel-goldschen-ohm/xarray-graph"
|
|
46
46
|
|
|
47
47
|
[project.scripts]
|
|
48
|
-
|
|
48
|
+
xarray-graph = "xarray_graph.__main__:main"
|
|
49
49
|
|
|
50
50
|
[build-system]
|
|
51
51
|
requires = [
|
|
@@ -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
|
|
|
@@ -1034,6 +1024,17 @@ class XarrayGraph(QMainWindow):
|
|
|
1034
1024
|
if i != index:
|
|
1035
1025
|
button.setChecked(False)
|
|
1036
1026
|
|
|
1027
|
+
def _show_control_panel_at(self, index: int) -> None:
|
|
1028
|
+
actions = self._control_panel_toolbar.actions()
|
|
1029
|
+
widgets = [self._control_panel_toolbar.widgetForAction(action) for action in actions]
|
|
1030
|
+
buttons = [widget for widget in widgets if isinstance(widget, QToolButton)]
|
|
1031
|
+
buttons[index].setChecked(True)
|
|
1032
|
+
self._control_panel.setCurrentIndex(index)
|
|
1033
|
+
self._control_panel.setVisible(True)
|
|
1034
|
+
for i, button in enumerate(buttons):
|
|
1035
|
+
if i != index:
|
|
1036
|
+
button.setChecked(False)
|
|
1037
|
+
|
|
1037
1038
|
def _setup_data_control_panel(self) -> None:
|
|
1038
1039
|
button = QToolButton()
|
|
1039
1040
|
button.setIcon(qta.icon('ph.eye', options=[{'opacity': 0.5}]))
|
|
@@ -1043,7 +1044,8 @@ class XarrayGraph(QMainWindow):
|
|
|
1043
1044
|
button.released.connect(lambda i=self._control_panel.count(): self._toggle_control_panel_at(i))
|
|
1044
1045
|
self._control_panel_toolbar.addWidget(button)
|
|
1045
1046
|
|
|
1046
|
-
self.
|
|
1047
|
+
self._data_treeviewer = XarrayTreeViewer()
|
|
1048
|
+
self._data_treeview = self._data_treeviewer.view()
|
|
1047
1049
|
self._data_treeview.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
|
1048
1050
|
root: XarrayTreeItem = XarrayTreeItem(node=self.data, key=None)
|
|
1049
1051
|
model: XarrayTreeModel = XarrayTreeModel(root)
|
|
@@ -1051,6 +1053,7 @@ class XarrayGraph(QMainWindow):
|
|
|
1051
1053
|
self._data_treeview.setShowCoords(False)
|
|
1052
1054
|
self._data_treeview.setModel(model)
|
|
1053
1055
|
self._data_treeview.selectionWasChanged.connect(self._on_tree_selection_changed)
|
|
1056
|
+
self._data_treeviewer.setSizes([100, 1])
|
|
1054
1057
|
|
|
1055
1058
|
self._xdim_combobox = QComboBox()
|
|
1056
1059
|
self._xdim_combobox.currentTextChanged.connect(self.set_xdim)
|
|
@@ -1060,7 +1063,7 @@ class XarrayGraph(QMainWindow):
|
|
|
1060
1063
|
vbox.setContentsMargins(0, 0, 0, 0)
|
|
1061
1064
|
vbox.setSpacing(0)
|
|
1062
1065
|
|
|
1063
|
-
vbox.addWidget(self.
|
|
1066
|
+
vbox.addWidget(self._data_treeviewer)
|
|
1064
1067
|
|
|
1065
1068
|
form = QFormLayout()
|
|
1066
1069
|
form.setContentsMargins(5, 3, 0, 3)
|
|
@@ -2196,43 +2199,41 @@ def permutations(coords: dict) -> list[dict]:
|
|
|
2196
2199
|
return permutations
|
|
2197
2200
|
|
|
2198
2201
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
#
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
-
# ds.attrs['notes'] = matdict['notes']
|
|
2235
|
-
# return ds, filepath
|
|
2202
|
+
def import_golab_tevc(filepath: str = '', parent: QWidget = None) -> tuple[xr.Dataset, str]:
|
|
2203
|
+
if filepath == '':
|
|
2204
|
+
filepath, _filter = QFileDialog.getOpenFileName(parent, 'Import GoLab TEVC', '', 'GoLab TEVC (*.mat)')
|
|
2205
|
+
if filepath == '':
|
|
2206
|
+
return None
|
|
2207
|
+
matdict = sp.io.loadmat(filepath, simplify_cells=True)
|
|
2208
|
+
# print(matdict)
|
|
2209
|
+
current = matdict['current']
|
|
2210
|
+
current_units = matdict['current_units']
|
|
2211
|
+
if len(current_units) > 1:
|
|
2212
|
+
prefix = current_units[0]
|
|
2213
|
+
if prefix in metric_scale_factors:
|
|
2214
|
+
current *= metric_scale_factors[prefix]
|
|
2215
|
+
current_units = current_units[1:]
|
|
2216
|
+
time = np.arange(len(current)) * matdict['time_interval_sec']
|
|
2217
|
+
ds = xr.Dataset(
|
|
2218
|
+
data_vars={
|
|
2219
|
+
'current': (['time'], current, {'units': current_units}),
|
|
2220
|
+
},
|
|
2221
|
+
coords={
|
|
2222
|
+
'time': (['time'], time, {'units': 's'}),
|
|
2223
|
+
},
|
|
2224
|
+
)
|
|
2225
|
+
if 'events' in matdict and matdict['events']:
|
|
2226
|
+
ds.attrs['regions'] = []
|
|
2227
|
+
for event in matdict['events']:
|
|
2228
|
+
time = event['time_sec']
|
|
2229
|
+
text = event['text']
|
|
2230
|
+
ds.attrs['regions'].append({
|
|
2231
|
+
'region': {'time': [time, time]},
|
|
2232
|
+
'text': text,
|
|
2233
|
+
})
|
|
2234
|
+
if 'notes' in matdict:
|
|
2235
|
+
ds.attrs['notes'] = matdict['notes']
|
|
2236
|
+
return ds, filepath
|
|
2236
2237
|
|
|
2237
2238
|
|
|
2238
2239
|
def test_live():
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from xarray_graph.XarrayGraph import XarrayGraph
|
|
2
|
+
from qtpy.QtCore import QTimer
|
|
3
|
+
from qtpy.QtWidgets import QApplication
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
app = QApplication()
|
|
8
|
+
ui = XarrayGraph()
|
|
9
|
+
# ui.setWindowTitle(ui.__class__.__name__)
|
|
10
|
+
ui.setWindowTitle('xarray-graph')
|
|
11
|
+
ui.show()
|
|
12
|
+
QTimer.singleShot(100, lambda: ask_for_example(ui))
|
|
13
|
+
app.exec()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ask_for_example(ui: XarrayGraph):
|
|
17
|
+
from qtpy.QtWidgets import QMessageBox
|
|
18
|
+
|
|
19
|
+
example = QMessageBox.question(ui, 'Example?', 'Load example data?')
|
|
20
|
+
if example == QMessageBox.StandardButton.Yes:
|
|
21
|
+
load_example(ui)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_example(ui: XarrayGraph):
|
|
25
|
+
import numpy as np
|
|
26
|
+
import xarray as xr
|
|
27
|
+
from datatree import DataTree
|
|
28
|
+
|
|
29
|
+
n = 100
|
|
30
|
+
raw_ds = xr.Dataset(
|
|
31
|
+
data_vars={
|
|
32
|
+
'current': (['series', 'sweep', 'time'], np.random.rand(3, 10, n) * 1e-9, {'units': 'A'}),
|
|
33
|
+
'voltage': (['series', 'sweep', 'time'], np.random.rand(3, 10, n) * 10000, {'units': 'V'}),
|
|
34
|
+
},
|
|
35
|
+
coords={
|
|
36
|
+
'series': ('series', np.arange(3)),
|
|
37
|
+
'sweep': ('sweep', np.arange(10)),
|
|
38
|
+
'time': ('time', np.arange(n) * 0.01, {'units': 's'}),
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
baselined_ds = xr.Dataset(
|
|
43
|
+
data_vars={
|
|
44
|
+
'current': (['series', 'sweep', 'time'], np.random.rand(3, 10, n) * 1e-9, {'units': 'A'}),
|
|
45
|
+
},
|
|
46
|
+
coords={
|
|
47
|
+
'series': ('series', np.arange(3)),
|
|
48
|
+
'sweep': ('sweep', np.arange(10)),
|
|
49
|
+
'time': ('time', np.arange(n) * 0.01, {'units': 's'}),
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
scaled_ds = xr.Dataset(
|
|
54
|
+
data_vars={
|
|
55
|
+
'current': (['series', 'sweep', 'time'], np.random.rand(1, 2, n) * 1e-9, {'units': 'A'}),
|
|
56
|
+
},
|
|
57
|
+
coords={
|
|
58
|
+
'series': ('series', [1]),
|
|
59
|
+
'sweep': ('sweep', [5,8]),
|
|
60
|
+
'time': ('time', np.arange(n) * 0.01, {'units': 's'}),
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
root_node = DataTree()
|
|
65
|
+
raw_node = DataTree(name='raw', data=raw_ds, parent=root_node)
|
|
66
|
+
baselined_node = DataTree(name='baselined', data=baselined_ds, parent=raw_node)
|
|
67
|
+
scaled_node = DataTree(name='scaled', data=scaled_ds, parent=baselined_node)
|
|
68
|
+
|
|
69
|
+
ui.data = root_node
|
|
70
|
+
|
|
71
|
+
ui._show_control_panel_at(0)
|
|
72
|
+
ui._data_treeview.expandAll()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == '__main__':
|
|
76
|
+
main()
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from xarray_graph.XarrayGraph import XarrayGraph
|
|
2
|
-
from qtpy.QtWidgets import QApplication
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def main():
|
|
6
|
-
app = QApplication()
|
|
7
|
-
ui = XarrayGraph()
|
|
8
|
-
ui.setWindowTitle(ui.__class__.__name__)
|
|
9
|
-
ui.show()
|
|
10
|
-
app.exec()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if __name__ == '__main__':
|
|
14
|
-
main()
|
|
File without changes
|
|
File without changes
|
{xarray_graph-0.2.0/src/xarray_graph → xarray_graph-0.2.2/src/xarray_graph/tmp}/RegionsManager.py
RENAMED
|
File without changes
|
|
File without changes
|