dcscope 2.22.1__py3-none-any.whl
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.
- dcscope/__init__.py +17 -0
- dcscope/__main__.py +44 -0
- dcscope/_version.py +21 -0
- dcscope/extensions.py +250 -0
- dcscope/gui/__init__.py +2 -0
- dcscope/gui/analysis/__init__.py +10 -0
- dcscope/gui/analysis/ana_basins.py +110 -0
- dcscope/gui/analysis/ana_basins.ui +143 -0
- dcscope/gui/analysis/ana_filter.py +341 -0
- dcscope/gui/analysis/ana_filter.ui +364 -0
- dcscope/gui/analysis/ana_log.py +122 -0
- dcscope/gui/analysis/ana_log.ui +100 -0
- dcscope/gui/analysis/ana_meta.py +207 -0
- dcscope/gui/analysis/ana_meta.ui +141 -0
- dcscope/gui/analysis/ana_plot.py +638 -0
- dcscope/gui/analysis/ana_plot.ui +1060 -0
- dcscope/gui/analysis/ana_slot.py +504 -0
- dcscope/gui/analysis/ana_slot.ui +684 -0
- dcscope/gui/analysis/ana_tables.py +227 -0
- dcscope/gui/analysis/ana_tables.ui +195 -0
- dcscope/gui/analysis/ana_view.py +69 -0
- dcscope/gui/analysis/ana_view.ui +269 -0
- dcscope/gui/analysis/dlg_slot_reorder.py +55 -0
- dcscope/gui/analysis/dlg_slot_reorder.ui +138 -0
- dcscope/gui/bulk/__init__.py +2 -0
- dcscope/gui/bulk/bulk_emodulus.py +200 -0
- dcscope/gui/bulk/bulk_emodulus.ui +245 -0
- dcscope/gui/compute/__init__.py +3 -0
- dcscope/gui/compute/comp_lme4.py +115 -0
- dcscope/gui/compute/comp_lme4.ui +194 -0
- dcscope/gui/compute/comp_lme4_dataset.py +51 -0
- dcscope/gui/compute/comp_lme4_dataset.ui +92 -0
- dcscope/gui/compute/comp_lme4_results.py +120 -0
- dcscope/gui/compute/comp_lme4_results.ui +268 -0
- dcscope/gui/compute/comp_stats.py +223 -0
- dcscope/gui/compute/comp_stats.ui +187 -0
- dcscope/gui/dcor/__init__.py +304 -0
- dcscope/gui/dcor/dcor.ui +118 -0
- dcscope/gui/export/__init__.py +3 -0
- dcscope/gui/export/e2data.py +394 -0
- dcscope/gui/export/e2data.ui +367 -0
- dcscope/gui/export/e2filter.py +76 -0
- dcscope/gui/export/e2filter.ui +111 -0
- dcscope/gui/export/e2plot.py +94 -0
- dcscope/gui/export/e2plot.ui +175 -0
- dcscope/gui/main.py +1089 -0
- dcscope/gui/main.ui +579 -0
- dcscope/gui/matrix/__init__.py +4 -0
- dcscope/gui/matrix/block_matrix.py +141 -0
- dcscope/gui/matrix/block_matrix.ui +207 -0
- dcscope/gui/matrix/data_matrix.py +574 -0
- dcscope/gui/matrix/dm_dataset.py +116 -0
- dcscope/gui/matrix/dm_dataset.ui +348 -0
- dcscope/gui/matrix/dm_element.py +111 -0
- dcscope/gui/matrix/dm_element.ui +169 -0
- dcscope/gui/matrix/dm_filter.py +115 -0
- dcscope/gui/matrix/dm_filter.ui +287 -0
- dcscope/gui/matrix/plot_matrix.py +316 -0
- dcscope/gui/matrix/pm_element.py +39 -0
- dcscope/gui/matrix/pm_plot.py +86 -0
- dcscope/gui/matrix/pm_plot.ui +249 -0
- dcscope/gui/pipeline_plot.py +797 -0
- dcscope/gui/pipeline_plot.ui +66 -0
- dcscope/gui/preferences.py +380 -0
- dcscope/gui/preferences.ui +554 -0
- dcscope/gui/quick_view/__init__.py +1 -0
- dcscope/gui/quick_view/qv_main.py +1072 -0
- dcscope/gui/quick_view/qv_main.ui +1297 -0
- dcscope/gui/quick_view/qv_scatter.py +284 -0
- dcscope/gui/quick_view/qv_style.css +10 -0
- dcscope/gui/update.py +90 -0
- dcscope/gui/widgets/__init__.py +15 -0
- dcscope/gui/widgets/bg_thread.py +29 -0
- dcscope/gui/widgets/bulk_list.py +79 -0
- dcscope/gui/widgets/bulk_list.ui +95 -0
- dcscope/gui/widgets/double_spin_box_nan.py +75 -0
- dcscope/gui/widgets/feature_combobox.py +140 -0
- dcscope/gui/widgets/get_path.py +42 -0
- dcscope/gui/widgets/key_value_table_widget.css +12 -0
- dcscope/gui/widgets/key_value_table_widget.py +75 -0
- dcscope/gui/widgets/mdi_subwindow_wo_close.py +12 -0
- dcscope/gui/widgets/qrangeslider.py +262 -0
- dcscope/gui/widgets/rangecontrol.py +308 -0
- dcscope/gui/widgets/rangecontrol.ui +136 -0
- dcscope/gui/widgets/simple_image_view.py +55 -0
- dcscope/gui/widgets/simple_plot_widget.py +113 -0
- dcscope/gui/widgets/so_colorbaritem.py +29 -0
- dcscope/gui/widgets/wait_cursor.py +33 -0
- dcscope/idiom.py +26 -0
- dcscope/img/__init__.py +2 -0
- dcscope/img/icon-theme/breeze/COPYING-ICONS +209 -0
- dcscope/img/icon-theme/breeze/COPYING.LIB +510 -0
- dcscope/img/icon-theme/breeze/README +5 -0
- dcscope/img/icon-theme/breeze/actions/16/application-exit.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/16/code-context.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-cancel.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-close.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-messages.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-ok-apply.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/dialog-ok.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/document-open-folder.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/document-open.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/document-save.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/documentinfo.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/16/draw-watercolor.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/edit-clear-all.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/edit-clear.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/edit-paste.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/globe.svg +16 -0
- dcscope/img/icon-theme/breeze/actions/16/gtk-preferences.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/list-add.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/messagebox_warning.svg +20 -0
- dcscope/img/icon-theme/breeze/actions/16/object-columns.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/object-order-lower.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/object-rows.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/office-chart-line-stacked.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/office-chart-ring.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/office-chart-scatter.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/path-mode-polyline.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/preferences-activities.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/remove.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/16/search.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/show-grid.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/special_paste.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/tools-wizard.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-calendar-list.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-filter.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-list-tree.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/view-statistics.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/16/visibility.svg +21 -0
- dcscope/img/icon-theme/breeze/actions/22/application-exit.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/22/code-context.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-cancel.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-close.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-messages.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-ok-apply.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/dialog-ok.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/document-open-folder.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/document-open.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/document-save.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/documentinfo.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/draw-watercolor.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/edit-clear-all.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/edit-clear.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/edit-paste.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/globe.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/22/gtk-preferences.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/22/list-add.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/messagebox_warning.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/object-columns.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/object-order-lower.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/object-rows.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/office-chart-line-stacked.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/office-chart-ring.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/office-chart-scatter.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/path-mode-polyline.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/22/preferences-activities.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/remove.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/22/special_paste.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/tools-wizard.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-calendar-list.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-filter.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-list-tree.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/view-statistics.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/22/visibility.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/application-exit.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/code-context.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-cancel.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-close.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-messages.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-ok-apply.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/dialog-ok.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/document-open-folder.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/document-open.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/document-save.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/documentinfo.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/draw-watercolor.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/edit-clear-all.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/edit-clear.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/edit-paste.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/globe.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/24/gtk-preferences.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/24/list-add.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/messagebox_warning.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/24/object-columns.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/object-order-lower.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/object-rows.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/office-chart-line-stacked.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/office-chart-ring.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/office-chart-scatter.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/path-mode-polyline.svg +15 -0
- dcscope/img/icon-theme/breeze/actions/24/preferences-activities.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/24/remove.svg +10 -0
- dcscope/img/icon-theme/breeze/actions/24/special_paste.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/tools-wizard.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-calendar-list.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-filter.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-list-tree.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/view-statistics.svg +12 -0
- dcscope/img/icon-theme/breeze/actions/24/visibility.svg +14 -0
- dcscope/img/icon-theme/breeze/actions/32/application-exit.svg +8 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-cancel.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-messages.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-ok-apply.svg +1 -0
- dcscope/img/icon-theme/breeze/actions/32/dialog-ok.svg +1 -0
- dcscope/img/icon-theme/breeze/actions/32/document-open-folder.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/document-open.svg +13 -0
- dcscope/img/icon-theme/breeze/actions/32/document-save.svg +18 -0
- dcscope/img/icon-theme/breeze/actions/32/gtk-preferences.svg +17 -0
- dcscope/img/icon-theme/breeze/actions/32/object-order-lower.svg +18 -0
- dcscope/img/icon-theme/breeze/actions/32/view-filter.svg +18 -0
- dcscope/img/icon-theme/breeze/actions/32/view-list-tree.svg +18 -0
- dcscope/img/icon-theme/breeze/places/16/folder-cloud.svg +13 -0
- dcscope/img/icon-theme/breeze/places/16/folder.svg +13 -0
- dcscope/img/icon-theme/breeze/places/22/folder-cloud.svg +13 -0
- dcscope/img/icon-theme/breeze/places/22/folder.svg +13 -0
- dcscope/img/icon-theme/breeze/places/24/folder-cloud.svg +12 -0
- dcscope/img/icon-theme/breeze/places/24/folder.svg +12 -0
- dcscope/img/icon-theme/breeze/places/32/folder-cloud.svg +35 -0
- dcscope/img/icon-theme/breeze/places/32/folder.svg +30 -0
- dcscope/img/icon-theme/breeze/places/64/folder-cloud.svg +35 -0
- dcscope/img/icon-theme/breeze/places/64/folder.svg +30 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-error.svg +9 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-information.svg +9 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-question.svg +7 -0
- dcscope/img/icon-theme/breeze/status/16/dialog-warning.svg +9 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-error.svg +9 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-information.svg +9 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-question.svg +7 -0
- dcscope/img/icon-theme/breeze/status/22/dialog-warning.svg +9 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-error.svg +13 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-information.svg +13 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-question.svg +9 -0
- dcscope/img/icon-theme/breeze/status/24/dialog-warning.svg +1 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-error.svg +25 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-information.svg +25 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-question.svg +25 -0
- dcscope/img/icon-theme/breeze/status/64/dialog-warning.svg +25 -0
- dcscope/img/icon-theme/collect_icons.py +124 -0
- dcscope/img/icon-theme/dcscope/dcor.png +0 -0
- dcscope/img/icon-theme/dcscope/edit-copy-anew.svg +13 -0
- dcscope/img/icon-theme/dcscope/emodulus.svg +69 -0
- dcscope/img/icon-theme/dcscope/filter_ray.svg +54 -0
- dcscope/img/icon-theme/dcscope/region_channel.svg +86 -0
- dcscope/img/icon-theme/dcscope/region_reservoir.svg +89 -0
- dcscope/img/icon-theme/dcscope/rlang.svg +108 -0
- dcscope/img/icon-theme/dcscope/statistical_significance.svg +106 -0
- dcscope/img/icon-theme/index.theme +81 -0
- dcscope/img/icon.png +0 -0
- dcscope/img/icon.svg +73 -0
- dcscope/img/splash.png +0 -0
- dcscope/meta_tool.py +143 -0
- dcscope/pipeline/__init__.py +5 -0
- dcscope/pipeline/core.py +632 -0
- dcscope/pipeline/dataslot.py +270 -0
- dcscope/pipeline/filter.py +161 -0
- dcscope/pipeline/filter_ray.py +170 -0
- dcscope/pipeline/plot.py +187 -0
- dcscope/plot_cache.py +79 -0
- dcscope/session.py +379 -0
- dcscope/util.py +34 -0
- dcscope-2.22.1.dist-info/METADATA +95 -0
- dcscope-2.22.1.dist-info/RECORD +267 -0
- dcscope-2.22.1.dist-info/WHEEL +5 -0
- dcscope-2.22.1.dist-info/entry_points.txt +2 -0
- dcscope-2.22.1.dist-info/licenses/LICENSE +621 -0
- dcscope-2.22.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import importlib.resources
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from PyQt6 import uic, QtCore, QtWidgets
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#: Precision for these features (``data``) should not go below this value.
|
|
8
|
+
#: (Precision is set automatically based on data range)
|
|
9
|
+
SPIN_CONTROL_PRECISION = {
|
|
10
|
+
"area_ratio": 3,
|
|
11
|
+
"aspect": 3,
|
|
12
|
+
"bright_avg": 1,
|
|
13
|
+
"bright_sd": 3,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RangeControl(QtWidgets.QWidget):
|
|
18
|
+
#: Emitted when the range changed
|
|
19
|
+
range_changed = QtCore.pyqtSignal(float, float)
|
|
20
|
+
|
|
21
|
+
def __init__(self, parent, label="feature", checkbox=True, integer=False,
|
|
22
|
+
data=None, *args, **kwargs):
|
|
23
|
+
super(RangeControl, self).__init__(parent=parent, *args, **kwargs)
|
|
24
|
+
ref = importlib.resources.files(
|
|
25
|
+
"dcscope.gui.widgets") / "rangecontrol.ui"
|
|
26
|
+
with importlib.resources.as_file(ref) as path_ui:
|
|
27
|
+
uic.loadUi(path_ui, self)
|
|
28
|
+
|
|
29
|
+
for spinbox in (self.doubleSpinBox_min, self.doubleSpinBox_max):
|
|
30
|
+
spinbox.setOpts(
|
|
31
|
+
format="{scaledValue:.{decimals}f}{suffixGap}{suffix}",
|
|
32
|
+
compactHeight=False,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# arbitrary data
|
|
36
|
+
self.data = data
|
|
37
|
+
|
|
38
|
+
# default values for limits
|
|
39
|
+
self.minimum = -100
|
|
40
|
+
self.maximum = +100
|
|
41
|
+
|
|
42
|
+
# label on top of control
|
|
43
|
+
self.setLabel(label)
|
|
44
|
+
|
|
45
|
+
# enable/disable checkbox
|
|
46
|
+
if not checkbox:
|
|
47
|
+
self.checkBox.hide()
|
|
48
|
+
|
|
49
|
+
# integer-valued control
|
|
50
|
+
if integer:
|
|
51
|
+
self.doubleSpinBox_min.setDecimals(1)
|
|
52
|
+
self.doubleSpinBox_max.setDecimals(1)
|
|
53
|
+
self.is_integer = integer
|
|
54
|
+
|
|
55
|
+
# reduce font size of name
|
|
56
|
+
font = self.label.font()
|
|
57
|
+
font.setPointSize(font.pointSize()-1)
|
|
58
|
+
self.label.setFont(font)
|
|
59
|
+
|
|
60
|
+
# signals
|
|
61
|
+
self.range_slider.rangeChanged.connect(self.on_range)
|
|
62
|
+
self.doubleSpinBox_min.valueChanged.connect(self.on_spinbox)
|
|
63
|
+
self.doubleSpinBox_max.valueChanged.connect(self.on_spinbox)
|
|
64
|
+
|
|
65
|
+
# call show to make sure slider is updated
|
|
66
|
+
self.show()
|
|
67
|
+
|
|
68
|
+
def read_pipeline_state(self):
|
|
69
|
+
state = {
|
|
70
|
+
"active": self.checkBox.isChecked(),
|
|
71
|
+
"start": self.doubleSpinBox_min.value(),
|
|
72
|
+
"end": self.doubleSpinBox_max.value(),
|
|
73
|
+
}
|
|
74
|
+
return state
|
|
75
|
+
|
|
76
|
+
def write_pipeline_state(self, state):
|
|
77
|
+
self.checkBox.setChecked(state["active"])
|
|
78
|
+
self.setSpinRange(state["start"], state["end"])
|
|
79
|
+
|
|
80
|
+
def check_boundary(self, old_value):
|
|
81
|
+
"""Make sure boundaries are properly set in the UI
|
|
82
|
+
|
|
83
|
+
The main purpose of this function is to fix #123. For integer
|
|
84
|
+
features we would like to have 0.5 step sizes to properly
|
|
85
|
+
filter out e.g. ML classes.
|
|
86
|
+
"""
|
|
87
|
+
if self.is_integer:
|
|
88
|
+
# force an increment of 0.5
|
|
89
|
+
new_value = round(old_value * 2) / 2
|
|
90
|
+
else:
|
|
91
|
+
new_value = old_value
|
|
92
|
+
return new_value
|
|
93
|
+
|
|
94
|
+
@QtCore.pyqtSlot(float, float)
|
|
95
|
+
def map_spin_values_to_range_slider(self):
|
|
96
|
+
"""Read values from spin controls and update the slider UI"""
|
|
97
|
+
# spin values
|
|
98
|
+
smin = self.doubleSpinBox_min.value()
|
|
99
|
+
smax = self.doubleSpinBox_max.value()
|
|
100
|
+
# limits
|
|
101
|
+
lmin = self.minimum
|
|
102
|
+
lmax = self.maximum
|
|
103
|
+
|
|
104
|
+
# current range slider limits [a.u.]
|
|
105
|
+
rmin = self.range_slider.min()
|
|
106
|
+
rmax = self.range_slider.max()
|
|
107
|
+
# ranges for translating to handle widths
|
|
108
|
+
dr = rmax - rmin # handle range
|
|
109
|
+
dl = lmax - lmin # value range
|
|
110
|
+
# range slider handles (not the limits)
|
|
111
|
+
if dl == 0:
|
|
112
|
+
hmin = hmax = 0
|
|
113
|
+
else:
|
|
114
|
+
hmin = rmin + (smin - lmin) * dr / dl
|
|
115
|
+
hmax = rmax - (lmax - smax) * dr / dl
|
|
116
|
+
if hmin < rmin:
|
|
117
|
+
hmin = 0
|
|
118
|
+
if hmax > rmax:
|
|
119
|
+
hmax = self.range_slider._INT_NUM
|
|
120
|
+
|
|
121
|
+
# make range selection stick tight to edges
|
|
122
|
+
if hmin < 10:
|
|
123
|
+
hmin = 0
|
|
124
|
+
if hmin > self.range_slider._INT_NUM - 10:
|
|
125
|
+
hmax = self.range_slider._INT_NUM
|
|
126
|
+
|
|
127
|
+
self.range_slider.update()
|
|
128
|
+
self.range_slider.blockSignals(True)
|
|
129
|
+
self.range_slider.setRange(hmin, hmax)
|
|
130
|
+
self.range_slider.blockSignals(False)
|
|
131
|
+
return hmin, hmax
|
|
132
|
+
|
|
133
|
+
@QtCore.pyqtSlot(int, int)
|
|
134
|
+
def map_range_slider_to_spin_values(self):
|
|
135
|
+
"""Return the respective value of the current range
|
|
136
|
+
|
|
137
|
+
Range limits are defined by
|
|
138
|
+
- self.doubleSpinBox_min.minimum()
|
|
139
|
+
- self.doubleSpinBox_min.maximum()
|
|
140
|
+
"""
|
|
141
|
+
# limits
|
|
142
|
+
lmin = self.minimum
|
|
143
|
+
lmax = self.maximum
|
|
144
|
+
# range slider limits
|
|
145
|
+
rmin = self.range_slider.min()
|
|
146
|
+
rmax = self.range_slider.max()
|
|
147
|
+
# range slider handles
|
|
148
|
+
hmin = self.range_slider.start()
|
|
149
|
+
hmax = self.range_slider.end()
|
|
150
|
+
# compute values
|
|
151
|
+
dr = rmax - rmin
|
|
152
|
+
dl = lmax - lmin
|
|
153
|
+
vmin = lmin + (hmin - rmin) * dl / dr
|
|
154
|
+
vmax = lmax - (rmax - hmax) * dl / dr
|
|
155
|
+
|
|
156
|
+
vmin = self.check_boundary(vmin)
|
|
157
|
+
vmax = self.check_boundary(vmax)
|
|
158
|
+
|
|
159
|
+
self.setSpinRange(vmin, vmax)
|
|
160
|
+
return vmin, vmax
|
|
161
|
+
|
|
162
|
+
def is_active(self):
|
|
163
|
+
return self.checkBox.isChecked()
|
|
164
|
+
|
|
165
|
+
@QtCore.pyqtSlot()
|
|
166
|
+
def on_range(self):
|
|
167
|
+
self.map_range_slider_to_spin_values()
|
|
168
|
+
self.range_changed.emit(self.doubleSpinBox_min.value(),
|
|
169
|
+
self.doubleSpinBox_max.value())
|
|
170
|
+
|
|
171
|
+
@QtCore.pyqtSlot()
|
|
172
|
+
def on_spinbox(self):
|
|
173
|
+
self.doubleSpinBox_min.setValue(
|
|
174
|
+
self.check_boundary(self.doubleSpinBox_min.value()))
|
|
175
|
+
self.doubleSpinBox_max.setValue(
|
|
176
|
+
self.check_boundary(self.doubleSpinBox_max.value()))
|
|
177
|
+
|
|
178
|
+
self.map_spin_values_to_range_slider()
|
|
179
|
+
self.range_changed.emit(self.doubleSpinBox_min.value(),
|
|
180
|
+
self.doubleSpinBox_max.value())
|
|
181
|
+
|
|
182
|
+
def reset_range(self):
|
|
183
|
+
self.doubleSpinBox_min.setValue(self.minimum)
|
|
184
|
+
self.doubleSpinBox_max.setValue(self.maximum)
|
|
185
|
+
|
|
186
|
+
def setActive(self, b=True):
|
|
187
|
+
self.checkBox.setChecked(b)
|
|
188
|
+
|
|
189
|
+
def setCheckable(self, b=True):
|
|
190
|
+
self.checkBox.setVisible(b)
|
|
191
|
+
|
|
192
|
+
def setInteger(self, b=True):
|
|
193
|
+
self.is_integer = b
|
|
194
|
+
|
|
195
|
+
def setLabel(self, label):
|
|
196
|
+
if label:
|
|
197
|
+
self.label.setText(label)
|
|
198
|
+
self.label.show()
|
|
199
|
+
else:
|
|
200
|
+
self.label.hide()
|
|
201
|
+
|
|
202
|
+
def setLimits(self, vmin, vmax, hard_limit=False):
|
|
203
|
+
"""Set the limits of the range control
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
vmin, vmax: float
|
|
208
|
+
Minimum and maximum values
|
|
209
|
+
hard_limit: bool
|
|
210
|
+
If set to True, the spin controls will have a hard limit
|
|
211
|
+
that matches vmin and vmax. If False (default), the
|
|
212
|
+
limit of the spin controls is larger, giving the user
|
|
213
|
+
a broader range.
|
|
214
|
+
"""
|
|
215
|
+
if vmin == vmax:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
self.minimum = vmin
|
|
219
|
+
self.maximum = vmax
|
|
220
|
+
|
|
221
|
+
if hard_limit:
|
|
222
|
+
vminh = vmin
|
|
223
|
+
vmaxh = vmax
|
|
224
|
+
else:
|
|
225
|
+
# estimate based on number of digits
|
|
226
|
+
if vmin >= 0:
|
|
227
|
+
vminh = 0
|
|
228
|
+
else:
|
|
229
|
+
lmin = int(np.ceil(np.log10(np.abs(vmin)))) + 1
|
|
230
|
+
vminh = -10**lmin
|
|
231
|
+
if vmax > 0:
|
|
232
|
+
lmax = int(np.ceil(np.log10(vmax))) + 1
|
|
233
|
+
vmaxh = 10**lmax
|
|
234
|
+
else:
|
|
235
|
+
vmaxh = 0
|
|
236
|
+
|
|
237
|
+
self.setSpinLimits(vmin=vminh, vmax=vmaxh)
|
|
238
|
+
# slider values
|
|
239
|
+
self.map_spin_values_to_range_slider()
|
|
240
|
+
|
|
241
|
+
def setSpinLimits(self, vmin, vmax):
|
|
242
|
+
"""Sets spin control limits and precision
|
|
243
|
+
|
|
244
|
+
Notes
|
|
245
|
+
-----
|
|
246
|
+
The precision is set automatically from the min/max
|
|
247
|
+
peak-to-peak size. If this precision is not high
|
|
248
|
+
enough for a specific application, you can set the
|
|
249
|
+
``data`` attribute upon initialization and add the
|
|
250
|
+
precision (in decimals) to :const:`SPIN_CONTROL_PRECISION`
|
|
251
|
+
via ``SPIN_CONTROL_PRECISION[data] = precision``.
|
|
252
|
+
"""
|
|
253
|
+
# min/max
|
|
254
|
+
self.doubleSpinBox_min.setMinimum(vmin)
|
|
255
|
+
self.doubleSpinBox_max.setMinimum(vmin)
|
|
256
|
+
self.doubleSpinBox_min.setMaximum(vmax)
|
|
257
|
+
self.doubleSpinBox_max.setMaximum(vmax)
|
|
258
|
+
|
|
259
|
+
# decimals
|
|
260
|
+
if not self.is_integer:
|
|
261
|
+
if vmax == vmin:
|
|
262
|
+
dec = 1
|
|
263
|
+
else:
|
|
264
|
+
# two significant digits
|
|
265
|
+
dec = int(np.ceil(np.log10(1/np.abs(vmax-vmin)))) + 5
|
|
266
|
+
if dec <= 0:
|
|
267
|
+
dec = 1
|
|
268
|
+
if self.data in SPIN_CONTROL_PRECISION:
|
|
269
|
+
dec = max(dec, SPIN_CONTROL_PRECISION[self.data])
|
|
270
|
+
self.doubleSpinBox_min.setDecimals(dec)
|
|
271
|
+
self.doubleSpinBox_max.setDecimals(dec)
|
|
272
|
+
self.doubleSpinBox_min.setSingleStep(10**-dec)
|
|
273
|
+
self.doubleSpinBox_max.setSingleStep(10**-dec)
|
|
274
|
+
|
|
275
|
+
def setSpinRange(self, vmin, vmax):
|
|
276
|
+
"""Set values of left and right spin controls (not the limits)
|
|
277
|
+
|
|
278
|
+
Extends the range if necessary
|
|
279
|
+
"""
|
|
280
|
+
limits_changed = False
|
|
281
|
+
|
|
282
|
+
if vmin < self.minimum:
|
|
283
|
+
limit_min = np.floor(vmin)
|
|
284
|
+
limits_changed = True
|
|
285
|
+
else:
|
|
286
|
+
limit_min = self.minimum
|
|
287
|
+
|
|
288
|
+
if vmax > self.maximum:
|
|
289
|
+
limit_max = np.ceil(vmax)
|
|
290
|
+
limits_changed = True
|
|
291
|
+
else:
|
|
292
|
+
limit_max = self.maximum
|
|
293
|
+
|
|
294
|
+
if limits_changed:
|
|
295
|
+
self.setLimits(limit_min, limit_max)
|
|
296
|
+
|
|
297
|
+
self.doubleSpinBox_min.blockSignals(True)
|
|
298
|
+
self.doubleSpinBox_max.blockSignals(True)
|
|
299
|
+
self.doubleSpinBox_min.setValue(vmin)
|
|
300
|
+
self.doubleSpinBox_max.setValue(vmax)
|
|
301
|
+
self.doubleSpinBox_min.blockSignals(False)
|
|
302
|
+
self.doubleSpinBox_max.blockSignals(False)
|
|
303
|
+
|
|
304
|
+
self.range_slider.blockSignals(True)
|
|
305
|
+
self.map_spin_values_to_range_slider()
|
|
306
|
+
self.range_slider.blockSignals(False)
|
|
307
|
+
|
|
308
|
+
self.range_changed.emit(vmin, vmax)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ui version="4.0">
|
|
3
|
+
<class>Form</class>
|
|
4
|
+
<widget class="QWidget" name="Form">
|
|
5
|
+
<property name="geometry">
|
|
6
|
+
<rect>
|
|
7
|
+
<x>0</x>
|
|
8
|
+
<y>0</y>
|
|
9
|
+
<width>481</width>
|
|
10
|
+
<height>45</height>
|
|
11
|
+
</rect>
|
|
12
|
+
</property>
|
|
13
|
+
<property name="sizePolicy">
|
|
14
|
+
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
|
15
|
+
<horstretch>0</horstretch>
|
|
16
|
+
<verstretch>0</verstretch>
|
|
17
|
+
</sizepolicy>
|
|
18
|
+
</property>
|
|
19
|
+
<property name="windowTitle">
|
|
20
|
+
<string>Form</string>
|
|
21
|
+
</property>
|
|
22
|
+
<layout class="QVBoxLayout" name="verticalLayout">
|
|
23
|
+
<property name="spacing">
|
|
24
|
+
<number>0</number>
|
|
25
|
+
</property>
|
|
26
|
+
<property name="leftMargin">
|
|
27
|
+
<number>0</number>
|
|
28
|
+
</property>
|
|
29
|
+
<property name="topMargin">
|
|
30
|
+
<number>0</number>
|
|
31
|
+
</property>
|
|
32
|
+
<property name="rightMargin">
|
|
33
|
+
<number>0</number>
|
|
34
|
+
</property>
|
|
35
|
+
<property name="bottomMargin">
|
|
36
|
+
<number>0</number>
|
|
37
|
+
</property>
|
|
38
|
+
<item>
|
|
39
|
+
<widget class="QLabel" name="label">
|
|
40
|
+
<property name="text">
|
|
41
|
+
<string>Feature description</string>
|
|
42
|
+
</property>
|
|
43
|
+
</widget>
|
|
44
|
+
</item>
|
|
45
|
+
<item>
|
|
46
|
+
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
47
|
+
<property name="spacing">
|
|
48
|
+
<number>6</number>
|
|
49
|
+
</property>
|
|
50
|
+
<item>
|
|
51
|
+
<widget class="QCheckBox" name="checkBox">
|
|
52
|
+
<property name="text">
|
|
53
|
+
<string/>
|
|
54
|
+
</property>
|
|
55
|
+
<property name="checked">
|
|
56
|
+
<bool>true</bool>
|
|
57
|
+
</property>
|
|
58
|
+
</widget>
|
|
59
|
+
</item>
|
|
60
|
+
<item>
|
|
61
|
+
<widget class="SpinBox" name="doubleSpinBox_min">
|
|
62
|
+
<property name="sizePolicy">
|
|
63
|
+
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
|
64
|
+
<horstretch>0</horstretch>
|
|
65
|
+
<verstretch>0</verstretch>
|
|
66
|
+
</sizepolicy>
|
|
67
|
+
</property>
|
|
68
|
+
<property name="minimumSize">
|
|
69
|
+
<size>
|
|
70
|
+
<width>0</width>
|
|
71
|
+
<height>0</height>
|
|
72
|
+
</size>
|
|
73
|
+
</property>
|
|
74
|
+
<property name="maximumSize">
|
|
75
|
+
<size>
|
|
76
|
+
<width>90</width>
|
|
77
|
+
<height>16777215</height>
|
|
78
|
+
</size>
|
|
79
|
+
</property>
|
|
80
|
+
<property name="decimals">
|
|
81
|
+
<number>2</number>
|
|
82
|
+
</property>
|
|
83
|
+
<property name="maximum">
|
|
84
|
+
<double>100.000000000000000</double>
|
|
85
|
+
</property>
|
|
86
|
+
</widget>
|
|
87
|
+
</item>
|
|
88
|
+
<item>
|
|
89
|
+
<widget class="QRangeSlider" name="range_slider" native="true">
|
|
90
|
+
<property name="sizePolicy">
|
|
91
|
+
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
|
92
|
+
<horstretch>0</horstretch>
|
|
93
|
+
<verstretch>0</verstretch>
|
|
94
|
+
</sizepolicy>
|
|
95
|
+
</property>
|
|
96
|
+
</widget>
|
|
97
|
+
</item>
|
|
98
|
+
<item>
|
|
99
|
+
<widget class="SpinBox" name="doubleSpinBox_max">
|
|
100
|
+
<property name="sizePolicy">
|
|
101
|
+
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
|
102
|
+
<horstretch>0</horstretch>
|
|
103
|
+
<verstretch>0</verstretch>
|
|
104
|
+
</sizepolicy>
|
|
105
|
+
</property>
|
|
106
|
+
<property name="maximumSize">
|
|
107
|
+
<size>
|
|
108
|
+
<width>90</width>
|
|
109
|
+
<height>16777215</height>
|
|
110
|
+
</size>
|
|
111
|
+
</property>
|
|
112
|
+
<property name="maximum">
|
|
113
|
+
<double>100.000000000000000</double>
|
|
114
|
+
</property>
|
|
115
|
+
</widget>
|
|
116
|
+
</item>
|
|
117
|
+
</layout>
|
|
118
|
+
</item>
|
|
119
|
+
</layout>
|
|
120
|
+
</widget>
|
|
121
|
+
<customwidgets>
|
|
122
|
+
<customwidget>
|
|
123
|
+
<class>QRangeSlider</class>
|
|
124
|
+
<extends>QWidget</extends>
|
|
125
|
+
<header>dcscope.gui.widgets</header>
|
|
126
|
+
<container>1</container>
|
|
127
|
+
</customwidget>
|
|
128
|
+
<customwidget>
|
|
129
|
+
<class>SpinBox</class>
|
|
130
|
+
<extends>QDoubleSpinBox</extends>
|
|
131
|
+
<header>pyqtgraph</header>
|
|
132
|
+
</customwidget>
|
|
133
|
+
</customwidgets>
|
|
134
|
+
<resources/>
|
|
135
|
+
<connections/>
|
|
136
|
+
</ui>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PyQt6 import QtCore, QtGui, QtWidgets
|
|
3
|
+
import pyqtgraph as pg
|
|
4
|
+
|
|
5
|
+
from .simple_plot_widget import SimpleViewBox
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SimpleImageView(pg.ImageView):
|
|
9
|
+
"""Custom class for data visualization in DCscope"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, *args, **kwargs):
|
|
12
|
+
super(SimpleImageView, self).__init__(view=SimpleImageViewBox(),
|
|
13
|
+
*args, **kwargs)
|
|
14
|
+
self.view.export.connect(self.on_export)
|
|
15
|
+
|
|
16
|
+
# disable pyqtgraph controls we don't need
|
|
17
|
+
self.ui.histogram.hide()
|
|
18
|
+
self.ui.roiBtn.hide()
|
|
19
|
+
self.ui.menuBtn.hide()
|
|
20
|
+
# disable keyboard shortcuts
|
|
21
|
+
self.keyPressEvent = lambda _: None
|
|
22
|
+
self.keyReleaseEvent = lambda _: None
|
|
23
|
+
|
|
24
|
+
def on_export(self, suffix):
|
|
25
|
+
assert suffix == "png"
|
|
26
|
+
path, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
27
|
+
self, '', '', 'PNG image (*.png)', '')
|
|
28
|
+
if not path.endswith(".png"):
|
|
29
|
+
path += ".png"
|
|
30
|
+
|
|
31
|
+
img = np.require(self.image, np.uint8, 'C')
|
|
32
|
+
height, width, _ = self.image.shape
|
|
33
|
+
|
|
34
|
+
qImg = QtGui.QImage(img, width, height, width *
|
|
35
|
+
3, QtGui.QImage.Format.Format_RGB888)
|
|
36
|
+
qImg.save(path)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SimpleImageViewBox(SimpleViewBox):
|
|
40
|
+
export = QtCore.pyqtSignal(str)
|
|
41
|
+
|
|
42
|
+
def autoRange(self, padding=0, *args, **kwargs):
|
|
43
|
+
super(SimpleImageViewBox, self).autoRange(padding=padding,
|
|
44
|
+
*args,
|
|
45
|
+
**kwargs)
|
|
46
|
+
|
|
47
|
+
def raiseContextMenu(self, ev):
|
|
48
|
+
menu = self.menu
|
|
49
|
+
menu.clear()
|
|
50
|
+
menu.addAction("Save event image as PNG",
|
|
51
|
+
lambda: self.export.emit("png"))
|
|
52
|
+
|
|
53
|
+
pos = ev.screenPos()
|
|
54
|
+
menu.popup(QtCore.QPoint(int(pos.x()), int(pos.y())))
|
|
55
|
+
return True
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from PyQt6 import QtCore, QtWidgets
|
|
2
|
+
import pyqtgraph as pg
|
|
3
|
+
from pyqtgraph import exporters
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SimplePlotItem(pg.PlotItem):
|
|
7
|
+
"""Custom class for data visualization in DCscope
|
|
8
|
+
|
|
9
|
+
Modifications include:
|
|
10
|
+
- right click menu only with "Export..."
|
|
11
|
+
- top and right axes
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, parent=None, *args, **kwargs):
|
|
15
|
+
if "viewBox" not in kwargs:
|
|
16
|
+
kwargs["viewBox"] = SimpleViewBox()
|
|
17
|
+
super(SimplePlotItem, self).__init__(parent, *args, **kwargs)
|
|
18
|
+
self.vb.export.connect(self.on_export)
|
|
19
|
+
# show top and right axes, but not ticklabels
|
|
20
|
+
for kax in ["top", "right"]:
|
|
21
|
+
self.showAxis(kax)
|
|
22
|
+
ax = self.axes[kax]["item"]
|
|
23
|
+
ax.setTicks([])
|
|
24
|
+
ax.setLabel(None)
|
|
25
|
+
ax.setStyle(tickTextOffset=0,
|
|
26
|
+
tickTextWidth=0,
|
|
27
|
+
tickTextHeight=0,
|
|
28
|
+
autoExpandTextSpace=False,
|
|
29
|
+
showValues=False,
|
|
30
|
+
)
|
|
31
|
+
# show grid
|
|
32
|
+
# https://github.com/DC-analysis/DCscope/issues/75
|
|
33
|
+
# self.showGrid(x=True, y=True, alpha=.1)
|
|
34
|
+
# visualization
|
|
35
|
+
self.hideButtons()
|
|
36
|
+
|
|
37
|
+
def axes_to_front(self):
|
|
38
|
+
"""Give the axes a high zValue"""
|
|
39
|
+
# bring axes to front
|
|
40
|
+
# (This screws up event selection in QuickView)
|
|
41
|
+
for kax in self.axes:
|
|
42
|
+
self.axes[kax]["item"].setZValue(900)
|
|
43
|
+
|
|
44
|
+
def on_export(self, suffix):
|
|
45
|
+
"""Export subplots as original figures (with axes labels, etc)"""
|
|
46
|
+
file, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
47
|
+
None,
|
|
48
|
+
'Save {} file'.format(suffix.upper()),
|
|
49
|
+
'',
|
|
50
|
+
'{} file (*.{})'.format(suffix.upper(), suffix))
|
|
51
|
+
if not file.endswith("." + suffix):
|
|
52
|
+
file += "." + suffix
|
|
53
|
+
self.perform_export(file)
|
|
54
|
+
|
|
55
|
+
def perform_export(self, file):
|
|
56
|
+
suffix = file[-3:]
|
|
57
|
+
if suffix == "png":
|
|
58
|
+
exp = exporters.ImageExporter(self)
|
|
59
|
+
# translate from screen resolution (80dpi) to 300dpi
|
|
60
|
+
exp.params["width"] = int(exp.params["width"] / 72 * 300)
|
|
61
|
+
elif suffix == "svg":
|
|
62
|
+
exp = exporters.SVGExporter(self)
|
|
63
|
+
exp.export(file)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SimplePlotWidget(pg.PlotWidget):
|
|
67
|
+
"""Custom class for data visualization in DCscope
|
|
68
|
+
|
|
69
|
+
Modifications include:
|
|
70
|
+
- white background
|
|
71
|
+
- those of SimplePlotItem
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, parent=None, background='w', **item_kwargs):
|
|
75
|
+
plot_item = SimplePlotItem(**item_kwargs)
|
|
76
|
+
super(SimplePlotWidget, self).__init__(parent,
|
|
77
|
+
background=background,
|
|
78
|
+
plotItem=plot_item)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class SimpleViewBox(pg.ViewBox):
|
|
82
|
+
export = QtCore.pyqtSignal(str)
|
|
83
|
+
|
|
84
|
+
def __init__(self, *args, **kwargs):
|
|
85
|
+
super(SimpleViewBox, self).__init__(*args, **kwargs)
|
|
86
|
+
#: allowed right-click menu options with new name
|
|
87
|
+
self.right_click_actions = {}
|
|
88
|
+
settings = QtCore.QSettings()
|
|
89
|
+
if int(settings.value("advanced/developer mode", 0)):
|
|
90
|
+
# Enable advanced export in developer mode
|
|
91
|
+
self.right_click_actions["Export..."] = "Advanced Export"
|
|
92
|
+
|
|
93
|
+
def raiseContextMenu(self, ev):
|
|
94
|
+
# Let the scene add on to the end of our context menu
|
|
95
|
+
menu = self.scene().addParentContextMenus(self, self.menu, ev)
|
|
96
|
+
|
|
97
|
+
# Only keep list of actions defined in `self.right_click_actions`
|
|
98
|
+
for action in self.menu.actions():
|
|
99
|
+
if action.text() in self.right_click_actions.values():
|
|
100
|
+
pass
|
|
101
|
+
elif action.text() not in self.right_click_actions:
|
|
102
|
+
self.menu.removeAction(action)
|
|
103
|
+
else:
|
|
104
|
+
action.setText(self.right_click_actions[action.text()])
|
|
105
|
+
|
|
106
|
+
menu.addAction("Export subplot as PNG",
|
|
107
|
+
lambda: self.export.emit("png"))
|
|
108
|
+
menu.addAction("Export subplot as SVG",
|
|
109
|
+
lambda: self.export.emit("svg"))
|
|
110
|
+
|
|
111
|
+
pos = ev.screenPos()
|
|
112
|
+
menu.popup(QtCore.QPoint(int(pos.x()), int(pos.y())))
|
|
113
|
+
return True
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import pyqtgraph as pg
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DCscopeColorBarItem(pg.ColorBarItem):
|
|
5
|
+
def __init__(self, yoffset, height, label, colorMap, *args, **kwargs):
|
|
6
|
+
"""pg.ColorBarItem modified for DCscope
|
|
7
|
+
|
|
8
|
+
- Added option to define height
|
|
9
|
+
- translate the colorbar so that it is aligned with the plot
|
|
10
|
+
- show the label on the right-hand axis
|
|
11
|
+
- increase the contents margins
|
|
12
|
+
"""
|
|
13
|
+
super(DCscopeColorBarItem, self).__init__(
|
|
14
|
+
colorMap=colorMap,
|
|
15
|
+
# TODO: Removing `cmap=colorMap` results in grayscale colormaps.
|
|
16
|
+
*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
# show label on right side
|
|
19
|
+
self.axis.setLabel(label)
|
|
20
|
+
|
|
21
|
+
# increase contents margins
|
|
22
|
+
self.layout.setContentsMargins(7, 0, 7, 0)
|
|
23
|
+
|
|
24
|
+
# set correct size and position
|
|
25
|
+
self.setFixedHeight(height)
|
|
26
|
+
|
|
27
|
+
tr = self.transform()
|
|
28
|
+
tr.translate(0, yoffset)
|
|
29
|
+
self.setTransform(tr)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
from PyQt6.QtWidgets import QApplication
|
|
4
|
+
from PyQt6.QtGui import QCursor
|
|
5
|
+
from PyQt6.QtCore import Qt, QEventLoop
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ShowWaitCursor(object):
|
|
9
|
+
def __enter__(self):
|
|
10
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
|
11
|
+
# This overloaded function call makes sure that all events,
|
|
12
|
+
# even those triggered during the function call, are processed.
|
|
13
|
+
QApplication.processEvents(QEventLoop.ProcessEventsFlag.AllEvents, 50)
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def __exit__(self, type, value, traceback):
|
|
17
|
+
QApplication.restoreOverrideCursor()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def show_wait_cursor(func):
|
|
21
|
+
"""A decorator that starts and stops a wait cursor for a function call
|
|
22
|
+
|
|
23
|
+
https://doc.qt.io/qt-5/qguiapplication.html#setOverrideCursor
|
|
24
|
+
Every setOverrideCursor() must eventually be followed by a
|
|
25
|
+
corresponding restoreOverrideCursor(), otherwise the stack
|
|
26
|
+
will never be emptied.
|
|
27
|
+
"""
|
|
28
|
+
@functools.wraps(func)
|
|
29
|
+
def wrapper(*args, **kwargs):
|
|
30
|
+
with ShowWaitCursor():
|
|
31
|
+
ret = func(*args, **kwargs)
|
|
32
|
+
return ret
|
|
33
|
+
return wrapper
|
dcscope/idiom.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from dclab.rtdc_dataset import feat_anc_core
|
|
2
|
+
|
|
3
|
+
#: integer-valued features
|
|
4
|
+
INTEGER_FEATURES = [
|
|
5
|
+
"fl1_max",
|
|
6
|
+
"fl1_npeaks",
|
|
7
|
+
"fl2_max",
|
|
8
|
+
"fl2_npeaks",
|
|
9
|
+
"fl3_max",
|
|
10
|
+
"fl3_npeaks",
|
|
11
|
+
"frame",
|
|
12
|
+
"index",
|
|
13
|
+
"ml_class",
|
|
14
|
+
"nevents",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
#: features that by definition trend over the course of a measurement
|
|
18
|
+
SLOPING_FEATURES = [
|
|
19
|
+
"frame",
|
|
20
|
+
"index",
|
|
21
|
+
"index_online",
|
|
22
|
+
"time",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
#: ancillary features that are easily computed
|
|
26
|
+
QUICK_FEATURES = feat_anc_core.FEATURES_RAPID
|