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
dcscope/gui/main.py
ADDED
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import importlib.resources
|
|
3
|
+
import signal
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
import warnings
|
|
7
|
+
import webbrowser
|
|
8
|
+
|
|
9
|
+
import dclab
|
|
10
|
+
from dclab.lme4 import rsetup
|
|
11
|
+
import h5py
|
|
12
|
+
import numpy
|
|
13
|
+
import scipy
|
|
14
|
+
|
|
15
|
+
from PyQt6 import uic, QtCore, QtGui, QtWidgets
|
|
16
|
+
from PyQt6.QtCore import QStandardPaths
|
|
17
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
18
|
+
import pyqtgraph as pg
|
|
19
|
+
|
|
20
|
+
from . import analysis
|
|
21
|
+
from . import bulk
|
|
22
|
+
from . import compute
|
|
23
|
+
from . import dcor
|
|
24
|
+
from . import export
|
|
25
|
+
from . import pipeline_plot
|
|
26
|
+
from . import preferences
|
|
27
|
+
from . import quick_view
|
|
28
|
+
from . import update
|
|
29
|
+
from . import widgets
|
|
30
|
+
|
|
31
|
+
from ..extensions import ExtensionManager
|
|
32
|
+
from .. import pipeline
|
|
33
|
+
from .. import session
|
|
34
|
+
|
|
35
|
+
from .._version import version
|
|
36
|
+
|
|
37
|
+
# global plotting configuration parameters
|
|
38
|
+
pg.setConfigOption("background", None)
|
|
39
|
+
pg.setConfigOption("foreground", "k")
|
|
40
|
+
pg.setConfigOption("antialias", True)
|
|
41
|
+
pg.setConfigOption("imageAxisOrder", "row-major")
|
|
42
|
+
|
|
43
|
+
# set Qt icon theme search path
|
|
44
|
+
ref = importlib.resources.files("dcscope.img") / "icon.png"
|
|
45
|
+
with importlib.resources.as_file(ref) as icon_path:
|
|
46
|
+
theme_path = icon_path.with_name("icon-theme")
|
|
47
|
+
if theme_path.exists():
|
|
48
|
+
QtGui.QIcon.setThemeSearchPaths([str(theme_path)])
|
|
49
|
+
QtGui.QIcon.setThemeName(".")
|
|
50
|
+
else:
|
|
51
|
+
warnings.warn("DCscope theme path not available")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DCscope(QtWidgets.QMainWindow):
|
|
55
|
+
plots_changed = QtCore.pyqtSignal()
|
|
56
|
+
|
|
57
|
+
def __init__(self, *arguments):
|
|
58
|
+
"""Initialize DCscope
|
|
59
|
+
|
|
60
|
+
If you pass the "--version" command line argument, the
|
|
61
|
+
application will print the version after initialization
|
|
62
|
+
and exit.
|
|
63
|
+
"""
|
|
64
|
+
super(DCscope, self).__init__()
|
|
65
|
+
ref = importlib.resources.files("dcscope.gui") / "main.ui"
|
|
66
|
+
with importlib.resources.as_file(ref) as path_ui:
|
|
67
|
+
uic.loadUi(path_ui, self)
|
|
68
|
+
|
|
69
|
+
# update check
|
|
70
|
+
self._update_thread = None
|
|
71
|
+
self._update_worker = None
|
|
72
|
+
# Settings are stored in the .ini file format. Even though
|
|
73
|
+
# `self.settings` may return integer/bool in the same session,
|
|
74
|
+
# in the next session, it will reliably return strings. Lists
|
|
75
|
+
# of strings (comma-separated) work nicely though.
|
|
76
|
+
QtCore.QCoreApplication.setOrganizationName("DC-analysis")
|
|
77
|
+
QtCore.QCoreApplication.setOrganizationDomain("dc-cosmos.org")
|
|
78
|
+
QtCore.QCoreApplication.setApplicationName("dcscope")
|
|
79
|
+
QtCore.QSettings.setDefaultFormat(QtCore.QSettings.Format.IniFormat)
|
|
80
|
+
|
|
81
|
+
#: DCscope settings
|
|
82
|
+
self.settings = QtCore.QSettings()
|
|
83
|
+
# Register custom DCOR CA bundle directory with dclab
|
|
84
|
+
ca_path = pathlib.Path(
|
|
85
|
+
QStandardPaths.writableLocation(
|
|
86
|
+
QStandardPaths.StandardLocation.AppDataLocation)
|
|
87
|
+
) / "certificates"
|
|
88
|
+
ca_path.mkdir(exist_ok=True, parents=True)
|
|
89
|
+
dclab.rtdc_dataset.fmt_dcor.DCOR_CERTS_SEARCH_PATHS.append(ca_path)
|
|
90
|
+
# Register user-defined DCOR API Key in case the user wants to
|
|
91
|
+
# open a session with private data.
|
|
92
|
+
api_key = self.settings.value("dcor/api key", "")
|
|
93
|
+
dclab.rtdc_dataset.fmt_dcor.api.APIHandler.add_api_key(api_key)
|
|
94
|
+
# Register S3 access settings in dclab
|
|
95
|
+
s3_endpoint_url = self.settings.value("s3/endpoint url", "")
|
|
96
|
+
if s3_endpoint_url:
|
|
97
|
+
dclab.rtdc_dataset.fmt_s3.S3_ENDPOINT_URL = s3_endpoint_url
|
|
98
|
+
s3_access_key_id = self.settings.value("s3/access key id", "")
|
|
99
|
+
if s3_access_key_id:
|
|
100
|
+
dclab.rtdc_dataset.fmt_s3.S3_ACCESS_KEY_ID = s3_access_key_id
|
|
101
|
+
s3_secret_access_key = self.settings.value("s3/secret access key", "")
|
|
102
|
+
if s3_secret_access_key:
|
|
103
|
+
dclab.rtdc_dataset.fmt_s3.S3_SECRET_ACCESS_KEY = \
|
|
104
|
+
s3_secret_access_key
|
|
105
|
+
|
|
106
|
+
#: Analysis pipeline
|
|
107
|
+
self.pipeline = pipeline.Pipeline()
|
|
108
|
+
#: Extensions
|
|
109
|
+
store_path = pathlib.Path(
|
|
110
|
+
QStandardPaths.writableLocation(
|
|
111
|
+
QStandardPaths.StandardLocation.AppDataLocation)
|
|
112
|
+
) / "extensions"
|
|
113
|
+
try:
|
|
114
|
+
self.extensions = ExtensionManager(store_path)
|
|
115
|
+
except BaseException:
|
|
116
|
+
QtWidgets.QMessageBox.warning(
|
|
117
|
+
self,
|
|
118
|
+
"Extensions automatically disabled",
|
|
119
|
+
"Some extensions could not be loaded and were disabled:\n\n"
|
|
120
|
+
+ traceback.format_exc(),
|
|
121
|
+
)
|
|
122
|
+
# GUI
|
|
123
|
+
self.setWindowTitle(f"DCscope {version}")
|
|
124
|
+
# Disable native menu bar (e.g. on Mac)
|
|
125
|
+
self.menubar.setNativeMenuBar(False)
|
|
126
|
+
# File menu
|
|
127
|
+
self.actionLoadDataset.triggered.connect(self.add_dataslot)
|
|
128
|
+
self.actionLoadDCOR.triggered.connect(self.on_action_dcor)
|
|
129
|
+
self.actionClearDatasets.triggered.connect(
|
|
130
|
+
self.on_action_clear_datasets)
|
|
131
|
+
self.actionClearSession.triggered.connect(self.on_action_clear)
|
|
132
|
+
self.actionOpenSession.triggered.connect(self.on_action_open)
|
|
133
|
+
self.actionQuit.triggered.connect(self.on_action_quit)
|
|
134
|
+
self.actionSaveSession.triggered.connect(self.on_action_save)
|
|
135
|
+
# Edit menu
|
|
136
|
+
self.actionChangeDatasetOrder.triggered.connect(
|
|
137
|
+
self.on_action_change_dataset_order)
|
|
138
|
+
self.actionPreferences.triggered.connect(self.on_action_preferences)
|
|
139
|
+
# Bulk action menu
|
|
140
|
+
self.actionComputeEmodulus.triggered.connect(
|
|
141
|
+
self.on_action_compute_emodulus)
|
|
142
|
+
# Compute menu
|
|
143
|
+
self.actionComputeStatistics.triggered.connect(
|
|
144
|
+
self.on_action_compute_statistics)
|
|
145
|
+
self.actionComputeSignificance.triggered.connect(
|
|
146
|
+
self.on_action_compute_significance)
|
|
147
|
+
# Export menu
|
|
148
|
+
# data
|
|
149
|
+
self.actionExportData.triggered.connect(self.on_action_export_data)
|
|
150
|
+
# filters
|
|
151
|
+
self.action_export_filter_polygon.triggered.connect(
|
|
152
|
+
self.on_action_export_filter_polygon)
|
|
153
|
+
self.action_export_filter_pipeline.triggered.connect(
|
|
154
|
+
self.on_action_export_filter_pipeline)
|
|
155
|
+
self.action_export_filter_ray_dataset.triggered.connect(
|
|
156
|
+
self.on_action_export_filter_ray_dataset)
|
|
157
|
+
# plot
|
|
158
|
+
self.actionExportPlot.triggered.connect(self.on_action_export_plot)
|
|
159
|
+
# Import menu
|
|
160
|
+
self.actionImportFilter.triggered.connect(self.on_action_import_filter)
|
|
161
|
+
# Help menu
|
|
162
|
+
self.actionDocumentation.triggered.connect(self.on_action_docs)
|
|
163
|
+
self.actionSoftware.triggered.connect(self.on_action_software)
|
|
164
|
+
self.actionAbout.triggered.connect(self.on_action_about)
|
|
165
|
+
# Subwindows
|
|
166
|
+
self.subwindows = {}
|
|
167
|
+
# Subwindows for plots
|
|
168
|
+
self.subwindows_plots = {}
|
|
169
|
+
# Initialize a few things
|
|
170
|
+
self.init_quick_view()
|
|
171
|
+
self.init_analysis_view()
|
|
172
|
+
self.mdiArea.cascadeSubWindows()
|
|
173
|
+
# BLOCK MATRIX (wraps DataMatrix and PlotMatrix)
|
|
174
|
+
# BlockMatrix appearance
|
|
175
|
+
self.toolButton_dm.clicked.connect(self.on_data_matrix)
|
|
176
|
+
self.splitter.splitterMoved.connect(self.on_splitter)
|
|
177
|
+
# BlockMatrix Actions
|
|
178
|
+
self.actionNewFilter.triggered.connect(self.add_filter)
|
|
179
|
+
self.actionNewPlot.triggered.connect(self.add_plot)
|
|
180
|
+
self.block_matrix.toolButton_load_dataset.clicked.connect(
|
|
181
|
+
self.add_dataslot)
|
|
182
|
+
self.block_matrix.toolButton_new_filter.clicked.connect(
|
|
183
|
+
self.add_filter)
|
|
184
|
+
self.block_matrix.toolButton_new_plot.clicked.connect(self.add_plot)
|
|
185
|
+
# BlockMatrix default state
|
|
186
|
+
self.toolButton_new_plot.setEnabled(False)
|
|
187
|
+
self.block_matrix.toolButton_new_plot.setEnabled(False)
|
|
188
|
+
# BlockMatrix other signals
|
|
189
|
+
self.block_matrix.pipeline_changed.connect(self.adopt_pipeline)
|
|
190
|
+
self.block_matrix.slot_modify_clicked.connect(self.on_modify_slot)
|
|
191
|
+
self.block_matrix.filter_modify_clicked.connect(self.on_modify_filter)
|
|
192
|
+
self.block_matrix.plot_modify_clicked.connect(self.on_modify_plot)
|
|
193
|
+
# ANALYSIS VIEW
|
|
194
|
+
self.widget_ana_view.set_pipeline(self.pipeline)
|
|
195
|
+
# filter signals
|
|
196
|
+
self.widget_ana_view.filter_changed.connect(self.adopt_filter)
|
|
197
|
+
self.widget_ana_view.pipeline_changed.connect(self.adopt_pipeline)
|
|
198
|
+
# polygon filter creation
|
|
199
|
+
self.widget_ana_view.widget_filter.request_new_polygon_filter.connect(
|
|
200
|
+
self.on_new_polygon_filter)
|
|
201
|
+
self.widget_quick_view.polygon_filter_about_to_be_deleted.connect(
|
|
202
|
+
self.on_remove_polygon_filter_from_pipeline)
|
|
203
|
+
self.widget_quick_view.polygon_filter_created.connect(
|
|
204
|
+
self.widget_ana_view.widget_filter.update_polygon_filters)
|
|
205
|
+
self.widget_quick_view.polygon_filter_modified.connect(
|
|
206
|
+
self.widget_ana_view.widget_filter.update_polygon_filters)
|
|
207
|
+
self.widget_quick_view.polygon_filter_modified.connect(
|
|
208
|
+
self.on_quickview_refresh) # might be an active filter (#26)
|
|
209
|
+
self.widget_quick_view.polygon_filter_modified.connect(
|
|
210
|
+
self.plots_changed) # might be an active filter (#26)
|
|
211
|
+
# This is important, because if metadata such as emodulus recipe
|
|
212
|
+
# is changed, the QuickView must be updated as well.
|
|
213
|
+
self.plots_changed.connect(self.widget_quick_view.plot)
|
|
214
|
+
# plot signals
|
|
215
|
+
self.widget_ana_view.plot_changed.connect(self.adopt_plot)
|
|
216
|
+
# slot signals
|
|
217
|
+
self.widget_ana_view.slot_changed.connect(self.adopt_slot)
|
|
218
|
+
# if "--version" was specified, print the version and exit
|
|
219
|
+
if "--version" in arguments:
|
|
220
|
+
print(version)
|
|
221
|
+
QtWidgets.QApplication.processEvents(
|
|
222
|
+
QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
|
|
223
|
+
sys.exit(0)
|
|
224
|
+
else:
|
|
225
|
+
# deal with any other arguments that might have been passed
|
|
226
|
+
for arg in arguments:
|
|
227
|
+
apath = pathlib.Path(arg)
|
|
228
|
+
if apath.exists():
|
|
229
|
+
if apath.suffix == ".so2":
|
|
230
|
+
# load a session
|
|
231
|
+
self.on_action_open(path=apath)
|
|
232
|
+
elif apath.suffix == ".rtdc":
|
|
233
|
+
# add a dataslot
|
|
234
|
+
self.add_dataslot(paths=[apath])
|
|
235
|
+
elif apath.suffix in [".sof", ".poly"]:
|
|
236
|
+
# add a filter
|
|
237
|
+
self.on_action_import_filter(path=apath)
|
|
238
|
+
|
|
239
|
+
# check for updates
|
|
240
|
+
do_update = int(self.settings.value("check for updates", 1))
|
|
241
|
+
self.on_action_check_update(do_update)
|
|
242
|
+
# finalize
|
|
243
|
+
self.show()
|
|
244
|
+
self.raise_()
|
|
245
|
+
self.activateWindow()
|
|
246
|
+
self.showMaximized()
|
|
247
|
+
self.setWindowState(QtCore.Qt.WindowState.WindowActive)
|
|
248
|
+
|
|
249
|
+
@widgets.show_wait_cursor
|
|
250
|
+
@QtCore.pyqtSlot(dict)
|
|
251
|
+
def adopt_filter(self, filt_state):
|
|
252
|
+
filt_id = filt_state["identifier"]
|
|
253
|
+
state = self.pipeline.__getstate__()
|
|
254
|
+
for ii in range(len(state["filters"])):
|
|
255
|
+
if state["filters"][ii]["identifier"] == filt_id:
|
|
256
|
+
state["filters"][ii] = filt_state
|
|
257
|
+
# make sure filters are enabled/disabled
|
|
258
|
+
if (filt_state["filter used"]
|
|
259
|
+
and filt_id not in state["filters used"]):
|
|
260
|
+
state["filters used"].append(filt_id)
|
|
261
|
+
elif (not filt_state["filter used"]
|
|
262
|
+
and filt_id in state["filters used"]):
|
|
263
|
+
state["filters used"].remove(filt_id)
|
|
264
|
+
break
|
|
265
|
+
else:
|
|
266
|
+
raise ValueError("Filter not in pipeline: {}".format(filt_id))
|
|
267
|
+
self.adopt_pipeline(state)
|
|
268
|
+
|
|
269
|
+
@widgets.show_wait_cursor
|
|
270
|
+
@QtCore.pyqtSlot(dict)
|
|
271
|
+
def adopt_pipeline(self, pipeline_state):
|
|
272
|
+
# If the number of subplots within a plot changed, update the
|
|
273
|
+
# plot size accordingly.
|
|
274
|
+
for plot_index, plot_id in enumerate(self.pipeline.plot_ids):
|
|
275
|
+
old_ncol, old_nrow = self.pipeline.get_plot_col_row_count(plot_id)
|
|
276
|
+
try:
|
|
277
|
+
new_ncol, new_nrow = self.pipeline.get_plot_col_row_count(
|
|
278
|
+
plot_id, pipeline_state)
|
|
279
|
+
lay = pipeline_state["plots"][plot_index]["layout"]
|
|
280
|
+
except (KeyError, IndexError):
|
|
281
|
+
# the plot was removed
|
|
282
|
+
continue
|
|
283
|
+
else:
|
|
284
|
+
# we are aiming for a square plot aspect ratio
|
|
285
|
+
plot_width = lay["size x"] / new_ncol
|
|
286
|
+
plot_height = lay["size y"] / new_nrow
|
|
287
|
+
if plot_width < 200:
|
|
288
|
+
lay["size x"] += 200 * (new_ncol - old_ncol)
|
|
289
|
+
if plot_height < 200:
|
|
290
|
+
lay["size y"] += 200*(new_nrow-old_nrow)
|
|
291
|
+
# set the new state of the pipeline
|
|
292
|
+
self.pipeline.__setstate__(pipeline_state)
|
|
293
|
+
# update BlockMatrix
|
|
294
|
+
if self.sender() != self.block_matrix:
|
|
295
|
+
# Update BlockMatrix
|
|
296
|
+
self.setUpdatesEnabled(False)
|
|
297
|
+
self.block_matrix.adopt_pipeline(pipeline_state)
|
|
298
|
+
self.setUpdatesEnabled(True)
|
|
299
|
+
# trigger redraw
|
|
300
|
+
self.block_matrix.update()
|
|
301
|
+
# Invalidate block matrix elements that do not make sense due to
|
|
302
|
+
# filtering or plotting features.
|
|
303
|
+
invalid_dm = []
|
|
304
|
+
invalid_pm = []
|
|
305
|
+
for slot_index, slot in enumerate(self.pipeline.slots):
|
|
306
|
+
ds = self.pipeline.get_dataset(slot_index=slot_index,
|
|
307
|
+
filt_index=None)
|
|
308
|
+
for filt in self.pipeline.filters:
|
|
309
|
+
# box filters
|
|
310
|
+
for feat in filt.boxdict:
|
|
311
|
+
if feat not in ds.features:
|
|
312
|
+
invalid_dm.append((slot.identifier, filt.identifier))
|
|
313
|
+
break
|
|
314
|
+
else:
|
|
315
|
+
# polygon filters
|
|
316
|
+
for pid in filt.polylist:
|
|
317
|
+
pf = dclab.PolygonFilter.get_instance_from_id(pid)
|
|
318
|
+
if (pf.axes[0] not in ds.features
|
|
319
|
+
or pf.axes[1] not in ds.features):
|
|
320
|
+
invalid_dm.append((slot.identifier,
|
|
321
|
+
filt.identifier))
|
|
322
|
+
break
|
|
323
|
+
for plot in self.pipeline.plots:
|
|
324
|
+
plot_state = plot.__getstate__()
|
|
325
|
+
if (plot_state["general"]["axis x"] not in ds
|
|
326
|
+
or plot_state["general"]["axis x"] not in ds):
|
|
327
|
+
invalid_pm.append((slot.identifier, plot.identifier))
|
|
328
|
+
self.block_matrix.invalidate_elements(invalid_dm, invalid_pm)
|
|
329
|
+
# Update AnalysisView
|
|
330
|
+
self.widget_ana_view.set_pipeline(self.pipeline)
|
|
331
|
+
# Update QuickView choices
|
|
332
|
+
self.widget_quick_view.update_feature_choices()
|
|
333
|
+
# update list of polygon filters in Quick View
|
|
334
|
+
self.widget_quick_view.update_polygon_panel()
|
|
335
|
+
# Show Plot Windows
|
|
336
|
+
# create and show
|
|
337
|
+
for plot_state in pipeline_state["plots"]:
|
|
338
|
+
self.add_plot_window(plot_state["identifier"])
|
|
339
|
+
# remove old plot subwindows
|
|
340
|
+
plot_ids = [pp["identifier"] for pp in pipeline_state["plots"]]
|
|
341
|
+
for plot_id in list(self.subwindows_plots.keys()):
|
|
342
|
+
if plot_id not in plot_ids:
|
|
343
|
+
sub = self.subwindows_plots.pop(plot_id)
|
|
344
|
+
for child in sub.children():
|
|
345
|
+
# disconnect signals
|
|
346
|
+
if isinstance(child, pipeline_plot.PipelinePlot):
|
|
347
|
+
self.plots_changed.disconnect(child.update_content)
|
|
348
|
+
break
|
|
349
|
+
sub.deleteLater()
|
|
350
|
+
self.plots_changed.emit()
|
|
351
|
+
self.widget_ana_view.widget_plot.update_content()
|
|
352
|
+
# Remove zombie slots
|
|
353
|
+
for slot_id in list(pipeline.Dataslot._instances.keys()):
|
|
354
|
+
if slot_id not in self.pipeline.slot_ids:
|
|
355
|
+
pipeline.Dataslot.remove_slot(slot_id)
|
|
356
|
+
# enable buttons
|
|
357
|
+
if self.pipeline.slots:
|
|
358
|
+
self.toolButton_new_plot.setEnabled(True)
|
|
359
|
+
self.block_matrix.toolButton_new_plot.setEnabled(True)
|
|
360
|
+
else:
|
|
361
|
+
self.toolButton_new_plot.setEnabled(False)
|
|
362
|
+
self.block_matrix.toolButton_new_plot.setEnabled(False)
|
|
363
|
+
# redraw
|
|
364
|
+
self.block_matrix.update()
|
|
365
|
+
self.mdiArea.update()
|
|
366
|
+
self.subwindows["analysis_view"].update()
|
|
367
|
+
|
|
368
|
+
@widgets.show_wait_cursor
|
|
369
|
+
@QtCore.pyqtSlot(dict)
|
|
370
|
+
def adopt_plot(self, plot_state):
|
|
371
|
+
plot_id = plot_state["identifier"]
|
|
372
|
+
state = self.pipeline.__getstate__()
|
|
373
|
+
for ii in range(len(state["plots"])):
|
|
374
|
+
if state["plots"][ii]["identifier"] == plot_id:
|
|
375
|
+
state["plots"][ii] = plot_state
|
|
376
|
+
break
|
|
377
|
+
else:
|
|
378
|
+
raise ValueError("Plot not in pipeline: {}".format(plot_id))
|
|
379
|
+
self.adopt_pipeline(state)
|
|
380
|
+
|
|
381
|
+
@widgets.show_wait_cursor
|
|
382
|
+
@QtCore.pyqtSlot(dict)
|
|
383
|
+
def adopt_slot(self, slot_state):
|
|
384
|
+
slot_id = slot_state["identifier"]
|
|
385
|
+
state = self.pipeline.__getstate__()
|
|
386
|
+
for ii in range(len(state["slots"])):
|
|
387
|
+
if state["slots"][ii]["identifier"] == slot_id:
|
|
388
|
+
state["slots"][ii] = slot_state
|
|
389
|
+
# make sure filters are enabled/disabled
|
|
390
|
+
if (slot_state["slot used"]
|
|
391
|
+
and slot_id not in state["slots used"]):
|
|
392
|
+
state["slots used"].append(slot_id)
|
|
393
|
+
elif (not slot_state["slot used"]
|
|
394
|
+
and slot_id in state["slots used"]):
|
|
395
|
+
state["slots used"].remove(slot_id)
|
|
396
|
+
break
|
|
397
|
+
else:
|
|
398
|
+
raise ValueError("Slot not in pipeline: {}".format(slot_id))
|
|
399
|
+
self.adopt_pipeline(state)
|
|
400
|
+
|
|
401
|
+
@widgets.show_wait_cursor
|
|
402
|
+
@QtCore.pyqtSlot()
|
|
403
|
+
def add_dataslot(self, paths=None, is_dcor=False):
|
|
404
|
+
"""Adds a dataslot to the pipeline
|
|
405
|
+
|
|
406
|
+
Parameters
|
|
407
|
+
----------
|
|
408
|
+
paths: list of str or list of pathlib.Path
|
|
409
|
+
If specified, no file dialog is displayed and the files
|
|
410
|
+
specified are loaded. Can also be DCOR URL.
|
|
411
|
+
is_dcor: bool
|
|
412
|
+
If set to True, `paths` will be treated as a list of
|
|
413
|
+
DCOR URLs. Does not have any effect if `paths` is None.
|
|
414
|
+
"""
|
|
415
|
+
if paths is None:
|
|
416
|
+
fnames, _ = QtWidgets.QFileDialog.getOpenFileNames(
|
|
417
|
+
parent=self,
|
|
418
|
+
caption="Select an RT-DC measurement",
|
|
419
|
+
directory=self.settings.value("paths/add dataset", ""),
|
|
420
|
+
filter="RT-DC Files (*.rtdc)")
|
|
421
|
+
else:
|
|
422
|
+
fnames = paths
|
|
423
|
+
|
|
424
|
+
if fnames:
|
|
425
|
+
self.toolButton_new_plot.setEnabled(True)
|
|
426
|
+
self.block_matrix.toolButton_new_plot.setEnabled(True)
|
|
427
|
+
|
|
428
|
+
failed_paths = []
|
|
429
|
+
|
|
430
|
+
slot_ids = []
|
|
431
|
+
# Create Dataslot instance and update block matrix
|
|
432
|
+
self.setUpdatesEnabled(False)
|
|
433
|
+
for fn in fnames:
|
|
434
|
+
if is_dcor:
|
|
435
|
+
path = fn
|
|
436
|
+
else:
|
|
437
|
+
path = pathlib.Path(fn)
|
|
438
|
+
self.settings.setValue("paths/add dataset", str(path.parent))
|
|
439
|
+
# add a filter if we don't have one already
|
|
440
|
+
if self.pipeline.num_filters == 0:
|
|
441
|
+
self.add_filter()
|
|
442
|
+
try:
|
|
443
|
+
slot_id = self.pipeline.add_slot(path=path)
|
|
444
|
+
except BaseException:
|
|
445
|
+
if len(fnames) == 1:
|
|
446
|
+
# Let the user know immediately
|
|
447
|
+
raise
|
|
448
|
+
else:
|
|
449
|
+
failed_paths.append(path)
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
self.block_matrix.add_dataset(slot_id=slot_id)
|
|
453
|
+
slot_ids.append(slot_id)
|
|
454
|
+
|
|
455
|
+
self.setUpdatesEnabled(True)
|
|
456
|
+
self.repaint()
|
|
457
|
+
|
|
458
|
+
# Update box filter limits
|
|
459
|
+
self.widget_ana_view.widget_filter.update_box_ranges()
|
|
460
|
+
# Update dataslot in analysis view
|
|
461
|
+
self.widget_ana_view.widget_slot.update_content()
|
|
462
|
+
# redraw
|
|
463
|
+
self.block_matrix.update()
|
|
464
|
+
|
|
465
|
+
if failed_paths:
|
|
466
|
+
failed_text = ("The following files could not be loaded. You can "
|
|
467
|
+
"open them individually to see the corresponding "
|
|
468
|
+
"error message during loading.\n")
|
|
469
|
+
for path in failed_paths:
|
|
470
|
+
failed_text += f"- {path}\n"
|
|
471
|
+
QtWidgets.QMessageBox.warning(self,
|
|
472
|
+
"Failed to load some datasets",
|
|
473
|
+
failed_text)
|
|
474
|
+
|
|
475
|
+
return slot_ids
|
|
476
|
+
|
|
477
|
+
@QtCore.pyqtSlot()
|
|
478
|
+
def add_filter(self):
|
|
479
|
+
"""Add a filter using tool buttons"""
|
|
480
|
+
filt_id = self.pipeline.add_filter()
|
|
481
|
+
self.block_matrix.add_filter(identifier=filt_id)
|
|
482
|
+
self.widget_ana_view.widget_filter.update_content()
|
|
483
|
+
# redraw
|
|
484
|
+
self.block_matrix.update()
|
|
485
|
+
return filt_id
|
|
486
|
+
|
|
487
|
+
@QtCore.pyqtSlot()
|
|
488
|
+
def add_plot(self):
|
|
489
|
+
plot_id = self.pipeline.add_plot()
|
|
490
|
+
self.block_matrix.add_plot(identifier=plot_id)
|
|
491
|
+
self.add_plot_window(plot_id)
|
|
492
|
+
# update UI contents
|
|
493
|
+
self.widget_ana_view.widget_plot.update_content()
|
|
494
|
+
# redraw
|
|
495
|
+
self.block_matrix.update()
|
|
496
|
+
return plot_id
|
|
497
|
+
|
|
498
|
+
@QtCore.pyqtSlot()
|
|
499
|
+
def add_plot_window(self, plot_id):
|
|
500
|
+
"""Create a plot window if necessary and show it"""
|
|
501
|
+
if plot_id in self.subwindows_plots:
|
|
502
|
+
sub = self.subwindows_plots[plot_id]
|
|
503
|
+
else:
|
|
504
|
+
# create subwindow
|
|
505
|
+
sub = widgets.MDISubWindowWOButtons(self)
|
|
506
|
+
pw = pipeline_plot.PipelinePlot(parent=sub,
|
|
507
|
+
pipeline=self.pipeline,
|
|
508
|
+
plot_id=plot_id)
|
|
509
|
+
self.plots_changed.connect(pw.update_content)
|
|
510
|
+
sub.setWidget(pw)
|
|
511
|
+
pw.update_content()
|
|
512
|
+
self.mdiArea.addSubWindow(sub)
|
|
513
|
+
self.subwindows_plots[plot_id] = sub
|
|
514
|
+
sub.show()
|
|
515
|
+
|
|
516
|
+
def closeEvent(self, event):
|
|
517
|
+
"""Determine what happens when the user wants to quit"""
|
|
518
|
+
if self.pipeline.slots or self.pipeline.filters:
|
|
519
|
+
if self.on_action_clear():
|
|
520
|
+
event.accept()
|
|
521
|
+
else:
|
|
522
|
+
event.ignore()
|
|
523
|
+
else:
|
|
524
|
+
event.accept()
|
|
525
|
+
|
|
526
|
+
def dragEnterEvent(self, e):
|
|
527
|
+
"""Whether files are accepted"""
|
|
528
|
+
if e.mimeData().hasUrls():
|
|
529
|
+
e.accept()
|
|
530
|
+
else:
|
|
531
|
+
e.ignore()
|
|
532
|
+
|
|
533
|
+
@widgets.show_wait_cursor
|
|
534
|
+
def dropEvent(self, e):
|
|
535
|
+
"""Add dropped files to view"""
|
|
536
|
+
urls = e.mimeData().urls()
|
|
537
|
+
if urls:
|
|
538
|
+
pathlist = []
|
|
539
|
+
is_dcor = bool(urls[0].host())
|
|
540
|
+
for ff in urls:
|
|
541
|
+
if is_dcor:
|
|
542
|
+
# DCOR data
|
|
543
|
+
pathlist.append(ff.toString())
|
|
544
|
+
else:
|
|
545
|
+
pp = pathlib.Path(ff.toLocalFile())
|
|
546
|
+
if pp.is_dir():
|
|
547
|
+
pathlist += list(pp.rglob("*.rtdc"))
|
|
548
|
+
elif pp.suffix == ".rtdc":
|
|
549
|
+
pathlist.append(pp)
|
|
550
|
+
pathlist = sorted(pathlist)
|
|
551
|
+
if pathlist:
|
|
552
|
+
self.add_dataslot(paths=pathlist, is_dcor=is_dcor)
|
|
553
|
+
|
|
554
|
+
def init_analysis_view(self):
|
|
555
|
+
sub = widgets.MDISubWindowWOButtons(self)
|
|
556
|
+
self.widget_ana_view = analysis.AnalysisView(parent=self)
|
|
557
|
+
sub.setWidget(self.widget_ana_view)
|
|
558
|
+
self.subwindows["analysis_view"] = sub
|
|
559
|
+
# signals
|
|
560
|
+
self.toolButton_ana_view.clicked.connect(sub.setVisible)
|
|
561
|
+
self.block_matrix.quickviewed.connect(
|
|
562
|
+
self.widget_ana_view.on_quickview)
|
|
563
|
+
# applying a new filter triggers updating QuickView
|
|
564
|
+
self.widget_ana_view.widget_filter.pushButton_apply.clicked.connect(
|
|
565
|
+
self.on_quickview_refresh)
|
|
566
|
+
sub.hide()
|
|
567
|
+
self.mdiArea.addSubWindow(sub)
|
|
568
|
+
|
|
569
|
+
def init_quick_view(self):
|
|
570
|
+
sub = widgets.MDISubWindowWOButtons(self)
|
|
571
|
+
self.widget_quick_view = quick_view.QuickView(parent=self)
|
|
572
|
+
sub.setWidget(self.widget_quick_view)
|
|
573
|
+
self.subwindows["quick_view"] = sub
|
|
574
|
+
# signals
|
|
575
|
+
self.toolButton_quick_view.clicked.connect(self.on_quickview)
|
|
576
|
+
self.block_matrix.quickviewed.connect(self.on_quickview_show_dataset)
|
|
577
|
+
sub.hide()
|
|
578
|
+
self.mdiArea.addSubWindow(sub)
|
|
579
|
+
|
|
580
|
+
@QtCore.pyqtSlot()
|
|
581
|
+
def on_action_about(self):
|
|
582
|
+
gh = "DC-analysis/DCscope"
|
|
583
|
+
rtd = "dcscope.readthedocs.io"
|
|
584
|
+
about_text = (
|
|
585
|
+
f"DCscope is a graphical user interface for the analysis "
|
|
586
|
+
f"and visualization of deformability cytometry data sets.<br><br>"
|
|
587
|
+
f"Author: Paul Müller<br>"
|
|
588
|
+
f"GitHub: <a href='https://github.com/{gh}'>{gh}</a><br>"
|
|
589
|
+
f"Documentation: <a href='https://{rtd}'>{rtd}</a><br>"
|
|
590
|
+
)
|
|
591
|
+
QtWidgets.QMessageBox.about(self, f"DCscope {version}", about_text)
|
|
592
|
+
|
|
593
|
+
@QtCore.pyqtSlot()
|
|
594
|
+
def on_action_change_dataset_order(self):
|
|
595
|
+
"""Show dialog for changing dataset order"""
|
|
596
|
+
dlg = analysis.DlgSlotReorder(self.pipeline, self)
|
|
597
|
+
dlg.pipeline_changed.connect(self.adopt_pipeline)
|
|
598
|
+
dlg.exec()
|
|
599
|
+
|
|
600
|
+
@QtCore.pyqtSlot(bool)
|
|
601
|
+
def on_action_check_update(self, b):
|
|
602
|
+
self.settings.setValue("check for updates", int(b))
|
|
603
|
+
if b and self._update_thread is None:
|
|
604
|
+
self._update_thread = QtCore.QThread()
|
|
605
|
+
self._update_worker = update.UpdateWorker()
|
|
606
|
+
self._update_worker.moveToThread(self._update_thread)
|
|
607
|
+
self._update_worker.finished.connect(self._update_thread.quit)
|
|
608
|
+
self._update_worker.data_ready.connect(
|
|
609
|
+
self.on_action_check_update_finished)
|
|
610
|
+
self._update_thread.start()
|
|
611
|
+
|
|
612
|
+
ghrepo = "DC-analysis/DCscope"
|
|
613
|
+
|
|
614
|
+
QtCore.QMetaObject.invokeMethod(
|
|
615
|
+
self._update_worker,
|
|
616
|
+
'processUpdate',
|
|
617
|
+
QtCore.Qt.ConnectionType.QueuedConnection,
|
|
618
|
+
QtCore.Q_ARG(str, version),
|
|
619
|
+
QtCore.Q_ARG(str, ghrepo),
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
@QtCore.pyqtSlot(dict)
|
|
623
|
+
def on_action_check_update_finished(self, mdict):
|
|
624
|
+
# cleanup
|
|
625
|
+
self._update_thread.quit()
|
|
626
|
+
self._update_thread.wait()
|
|
627
|
+
self._update_worker = None
|
|
628
|
+
self._update_thread = None
|
|
629
|
+
# display message box
|
|
630
|
+
ver = mdict["version"]
|
|
631
|
+
web = mdict["releases url"]
|
|
632
|
+
dlb = mdict["binary url"]
|
|
633
|
+
msg = QtWidgets.QMessageBox()
|
|
634
|
+
msg.setWindowTitle("DCscope {} available!".format(ver))
|
|
635
|
+
msg.setTextFormat(QtCore.Qt.TextFormat.RichText)
|
|
636
|
+
text = "You can install DCscope {} ".format(ver)
|
|
637
|
+
if dlb is not None:
|
|
638
|
+
text += 'from a <a href="{}">direct download</a>. '.format(dlb)
|
|
639
|
+
else:
|
|
640
|
+
text += 'by running `pip install --upgrade dcscope`. '
|
|
641
|
+
text += 'Visit the <a href="{}">official release page</a>!'.format(web)
|
|
642
|
+
msg.setText(text)
|
|
643
|
+
msg.exec()
|
|
644
|
+
|
|
645
|
+
@QtCore.pyqtSlot()
|
|
646
|
+
def on_action_compute_emodulus(self):
|
|
647
|
+
dlg = bulk.BulkActionEmodulus(self, pipeline=self.pipeline)
|
|
648
|
+
dlg.pipeline_changed.connect(self.adopt_pipeline)
|
|
649
|
+
dlg.exec()
|
|
650
|
+
|
|
651
|
+
@QtCore.pyqtSlot()
|
|
652
|
+
def on_action_compute_significance(self):
|
|
653
|
+
# check that R is available
|
|
654
|
+
if rsetup.has_r():
|
|
655
|
+
dlg = compute.ComputeSignificance(self, pipeline=self.pipeline)
|
|
656
|
+
dlg.exec()
|
|
657
|
+
else:
|
|
658
|
+
QtWidgets.QMessageBox.critical(
|
|
659
|
+
self, "R not found!",
|
|
660
|
+
"The R executable was not found. Please add it "
|
|
661
|
+
+ "to the PATH variable or define it manually in the "
|
|
662
|
+
+ "DCscope preferences.")
|
|
663
|
+
|
|
664
|
+
@QtCore.pyqtSlot()
|
|
665
|
+
def on_action_compute_statistics(self):
|
|
666
|
+
dlg = compute.ComputeStatistics(self, pipeline=self.pipeline)
|
|
667
|
+
dlg.exec()
|
|
668
|
+
|
|
669
|
+
@QtCore.pyqtSlot()
|
|
670
|
+
def on_action_clear(self):
|
|
671
|
+
"""Clear the entire session"""
|
|
672
|
+
if bool(int(self.settings.value("advanced/user confirm clear", "1"))):
|
|
673
|
+
button_reply = QtWidgets.QMessageBox.question(
|
|
674
|
+
self, 'Clear Session', "All progress will be lost. Continue?",
|
|
675
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
676
|
+
QtWidgets.QMessageBox.StandardButton.No)
|
|
677
|
+
yes = button_reply == QtWidgets.QMessageBox.StandardButton.Yes
|
|
678
|
+
else:
|
|
679
|
+
yes = True
|
|
680
|
+
if yes:
|
|
681
|
+
session.clear_session(self.pipeline)
|
|
682
|
+
self.reload_pipeline()
|
|
683
|
+
self.setWindowTitle(f"DCscope {version}")
|
|
684
|
+
return yes
|
|
685
|
+
|
|
686
|
+
@QtCore.pyqtSlot()
|
|
687
|
+
def on_action_clear_datasets(self):
|
|
688
|
+
"""Clear only the datasets"""
|
|
689
|
+
if bool(int(self.settings.value("advanced/user confirm clear", "1"))):
|
|
690
|
+
button_reply = QtWidgets.QMessageBox.question(
|
|
691
|
+
self, 'Clear Datasets',
|
|
692
|
+
"Remove all datasets from this session?",
|
|
693
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
694
|
+
QtWidgets.QMessageBox.StandardButton.No)
|
|
695
|
+
yes = button_reply == QtWidgets.QMessageBox.StandardButton.Yes
|
|
696
|
+
else:
|
|
697
|
+
yes = True
|
|
698
|
+
if yes:
|
|
699
|
+
for slot_id in self.pipeline.slot_ids:
|
|
700
|
+
self.pipeline.remove_slot(slot_id)
|
|
701
|
+
self.reload_pipeline()
|
|
702
|
+
return yes
|
|
703
|
+
|
|
704
|
+
@QtCore.pyqtSlot()
|
|
705
|
+
def on_action_dcor(self):
|
|
706
|
+
"""Show the DCOR import dialog"""
|
|
707
|
+
dlg = dcor.DCORLoader(self)
|
|
708
|
+
dlg.exec()
|
|
709
|
+
|
|
710
|
+
@QtCore.pyqtSlot()
|
|
711
|
+
def on_action_docs(self):
|
|
712
|
+
webbrowser.open("https://dcscope.readthedocs.io")
|
|
713
|
+
|
|
714
|
+
@QtCore.pyqtSlot()
|
|
715
|
+
def on_action_export_data(self):
|
|
716
|
+
dlg = export.ExportData(self, pipeline=self.pipeline)
|
|
717
|
+
if dlg.path is not None: # user pressed cancel
|
|
718
|
+
dlg.exec()
|
|
719
|
+
|
|
720
|
+
@QtCore.pyqtSlot()
|
|
721
|
+
def on_action_export_filter_pipeline(self):
|
|
722
|
+
"""Export the entire filter pipeline"""
|
|
723
|
+
dlg = export.ExportFilter(self,
|
|
724
|
+
pipeline=self.pipeline,
|
|
725
|
+
file_format="sof")
|
|
726
|
+
dlg.exec()
|
|
727
|
+
return dlg # for testing
|
|
728
|
+
|
|
729
|
+
@QtCore.pyqtSlot()
|
|
730
|
+
def on_action_export_filter_polygon(self, exec_dialog=True):
|
|
731
|
+
"""Export only polygon filters of the current pipeline"""
|
|
732
|
+
dlg = export.ExportFilter(self,
|
|
733
|
+
pipeline=self.pipeline,
|
|
734
|
+
file_format="poly")
|
|
735
|
+
dlg.exec()
|
|
736
|
+
return dlg # for testing
|
|
737
|
+
|
|
738
|
+
@QtCore.pyqtSlot()
|
|
739
|
+
def on_action_export_filter_ray_dataset(self):
|
|
740
|
+
"""Export the filter ray for each dataset to the dataset location"""
|
|
741
|
+
#: dictionary with paths as keys and filter ID list as values
|
|
742
|
+
filt_dict = {}
|
|
743
|
+
#: paths that already exist (user will be asked to override)
|
|
744
|
+
existing_paths = []
|
|
745
|
+
#: keeps track of datasets that are loaded twice (will notify user)
|
|
746
|
+
double_paths = []
|
|
747
|
+
#: keeps track of DCOR data
|
|
748
|
+
dcor_data = []
|
|
749
|
+
for slot in self.pipeline.slots:
|
|
750
|
+
if slot.format == "dcor":
|
|
751
|
+
dcor_data.append(slot.path)
|
|
752
|
+
continue
|
|
753
|
+
ray_path = slot.path.with_suffix(".sof")
|
|
754
|
+
if ray_path in filt_dict:
|
|
755
|
+
double_paths.append(ray_path)
|
|
756
|
+
continue
|
|
757
|
+
if ray_path.exists():
|
|
758
|
+
existing_paths.append(ray_path)
|
|
759
|
+
filters = self.pipeline.get_filters_for_slot(slot.identifier)
|
|
760
|
+
filt_dict[ray_path] = [ff.identifier for ff in filters]
|
|
761
|
+
if dcor_data:
|
|
762
|
+
QtWidgets.QMessageBox.warning(
|
|
763
|
+
self,
|
|
764
|
+
"Some datasets are DCOR data for which no filter rays ",
|
|
765
|
+
"will be exported:\n\n"
|
|
766
|
+
+ "\n".join([str(p) for p in list(set(dcor_data))])
|
|
767
|
+
)
|
|
768
|
+
if double_paths:
|
|
769
|
+
QtWidgets.QMessageBox.warning(
|
|
770
|
+
self,
|
|
771
|
+
"Same datasets loaded in different slots",
|
|
772
|
+
"The following datasets are loaded twice. Only the first "
|
|
773
|
+
+ "filter ray will be saved:\n\n"
|
|
774
|
+
+ "\n".join([str(p) for p in list(set(double_paths))])
|
|
775
|
+
)
|
|
776
|
+
if existing_paths:
|
|
777
|
+
resp = QtWidgets.QMessageBox.question(
|
|
778
|
+
self,
|
|
779
|
+
"Override existing files?",
|
|
780
|
+
"The following files already exist, override?\n\n"
|
|
781
|
+
+ "\n".join([str(p) for p in existing_paths])
|
|
782
|
+
)
|
|
783
|
+
if resp:
|
|
784
|
+
# if the user agrees, these files will be overridden
|
|
785
|
+
existing_paths.clear()
|
|
786
|
+
for path in filt_dict:
|
|
787
|
+
if path in existing_paths:
|
|
788
|
+
# not overriding this one
|
|
789
|
+
continue
|
|
790
|
+
else:
|
|
791
|
+
session.export_filters(path, self.pipeline, filt_dict[path])
|
|
792
|
+
|
|
793
|
+
@QtCore.pyqtSlot()
|
|
794
|
+
def on_action_export_plot(self):
|
|
795
|
+
dlg = export.ExportPlot(self, pipeline=self.pipeline)
|
|
796
|
+
dlg.exec()
|
|
797
|
+
|
|
798
|
+
@QtCore.pyqtSlot()
|
|
799
|
+
def on_action_import_filter(self, path=None):
|
|
800
|
+
"""Import a filter from a .sof or .poly file
|
|
801
|
+
|
|
802
|
+
Parameters
|
|
803
|
+
----------
|
|
804
|
+
path: str or pathlib.Path
|
|
805
|
+
path to the filter to impoert
|
|
806
|
+
"""
|
|
807
|
+
if path is None:
|
|
808
|
+
path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
809
|
+
self, 'Select Filter', '', 'Filters formats (*.poly *.sof)')
|
|
810
|
+
if path:
|
|
811
|
+
session.import_filters(path, self.pipeline)
|
|
812
|
+
# update UI
|
|
813
|
+
self.reload_pipeline()
|
|
814
|
+
|
|
815
|
+
@QtCore.pyqtSlot()
|
|
816
|
+
def on_action_open(self, path=None):
|
|
817
|
+
"""Open a DCscope session"""
|
|
818
|
+
if self.pipeline.slots or self.pipeline.filters:
|
|
819
|
+
if not self.on_action_clear():
|
|
820
|
+
return
|
|
821
|
+
if path is None:
|
|
822
|
+
path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
823
|
+
self, 'Open session', '', 'DCscope session (*.so2)',
|
|
824
|
+
'DCscope session (*.so2)',
|
|
825
|
+
QtWidgets.QFileDialog.Option.DontUseNativeDialog)
|
|
826
|
+
if path:
|
|
827
|
+
search_paths = []
|
|
828
|
+
while True:
|
|
829
|
+
try:
|
|
830
|
+
with widgets.ShowWaitCursor():
|
|
831
|
+
session.open_session(path, self.pipeline, search_paths)
|
|
832
|
+
except session.DataFileNotFoundError as e:
|
|
833
|
+
missds = "\r".join([str(pp) for pp in e.missing_paths])
|
|
834
|
+
msg = QtWidgets.QMessageBox()
|
|
835
|
+
msg.setIcon(QtWidgets.QMessageBox.Icon.Warning)
|
|
836
|
+
msg.setText("Some datasets were not found! "
|
|
837
|
+
+ "Please specify a search location.")
|
|
838
|
+
msg.setWindowTitle(
|
|
839
|
+
"Missing {} dataset(s)".format(len(e.missing_paths)))
|
|
840
|
+
msg.setDetailedText("Missing files: \n\n" + missds)
|
|
841
|
+
msg.exec()
|
|
842
|
+
spath = QtWidgets.QFileDialog.getExistingDirectory(
|
|
843
|
+
self, 'Data search path')
|
|
844
|
+
if spath:
|
|
845
|
+
search_paths.append(spath)
|
|
846
|
+
else:
|
|
847
|
+
break
|
|
848
|
+
else:
|
|
849
|
+
break
|
|
850
|
+
self.show()
|
|
851
|
+
self.reload_pipeline()
|
|
852
|
+
self.setWindowTitle(
|
|
853
|
+
f"{pathlib.Path(path).name} [DCscope {version}]")
|
|
854
|
+
|
|
855
|
+
@QtCore.pyqtSlot()
|
|
856
|
+
def on_action_preferences(self):
|
|
857
|
+
"""Show the preferences dialog"""
|
|
858
|
+
dlg = preferences.Preferences(self)
|
|
859
|
+
dlg.setWindowTitle("DCscope Preferences")
|
|
860
|
+
dlg.feature_changed.connect(self.plots_changed)
|
|
861
|
+
dlg.feature_changed.connect(self.on_quickview_refresh)
|
|
862
|
+
dlg.exec()
|
|
863
|
+
|
|
864
|
+
@QtCore.pyqtSlot()
|
|
865
|
+
def on_action_quit(self):
|
|
866
|
+
"""Determine what happens when the user wants to quit"""
|
|
867
|
+
if self.pipeline.slots or self.pipeline.filters:
|
|
868
|
+
if not self.on_action_clear():
|
|
869
|
+
return
|
|
870
|
+
QtCore.QCoreApplication.quit()
|
|
871
|
+
|
|
872
|
+
@QtCore.pyqtSlot()
|
|
873
|
+
def on_action_save(self):
|
|
874
|
+
path, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
875
|
+
self, 'Save session', '', 'DCscope session (*.so2)')
|
|
876
|
+
if path:
|
|
877
|
+
if not path.endswith(".so2"):
|
|
878
|
+
path += ".so2"
|
|
879
|
+
session.save_session(path, self.pipeline)
|
|
880
|
+
self.setWindowTitle(
|
|
881
|
+
f"{pathlib.Path(path).name} [DCscope {version}]")
|
|
882
|
+
|
|
883
|
+
@QtCore.pyqtSlot()
|
|
884
|
+
def on_action_software(self):
|
|
885
|
+
libs = [dclab,
|
|
886
|
+
h5py,
|
|
887
|
+
numpy,
|
|
888
|
+
pg,
|
|
889
|
+
scipy,
|
|
890
|
+
]
|
|
891
|
+
|
|
892
|
+
sw_text = f"DCscope {version}\n\n"
|
|
893
|
+
sw_text += f"Python {sys.version}\n\n"
|
|
894
|
+
sw_text += "Modules:\n"
|
|
895
|
+
for lib in libs:
|
|
896
|
+
sw_text += f"- {lib.__name__} {lib.__version__}\n"
|
|
897
|
+
sw_text += f"- PyQt6 {QtCore.QT_VERSION_STR}\n" # Extrawurst
|
|
898
|
+
sw_text += "\n Breeze icon theme by the KDE Community (LGPL)."
|
|
899
|
+
if hasattr(sys, 'frozen'):
|
|
900
|
+
sw_text += "\nThis executable has been created using PyInstaller."
|
|
901
|
+
QtWidgets.QMessageBox.information(self, "Software", sw_text)
|
|
902
|
+
|
|
903
|
+
@QtCore.pyqtSlot(int)
|
|
904
|
+
def on_remove_polygon_filter_from_pipeline(self, pf_id):
|
|
905
|
+
"""Remove a polygon filter from all filters in the pipeline"""
|
|
906
|
+
for filt in self.pipeline.filters:
|
|
907
|
+
if pf_id in filt.polylist:
|
|
908
|
+
filt.polylist.remove(pf_id)
|
|
909
|
+
|
|
910
|
+
@widgets.show_wait_cursor
|
|
911
|
+
@QtCore.pyqtSlot()
|
|
912
|
+
def on_data_matrix(self):
|
|
913
|
+
"""Show/hide data matrix (User clicked Data Matrix button)"""
|
|
914
|
+
if self.toolButton_dm.isChecked():
|
|
915
|
+
self.splitter.setSizes([200, 1000])
|
|
916
|
+
else:
|
|
917
|
+
self.splitter.setSizes([0, 1])
|
|
918
|
+
# redraw
|
|
919
|
+
self.splitter.update()
|
|
920
|
+
self.mdiArea.update()
|
|
921
|
+
self.block_matrix.update()
|
|
922
|
+
|
|
923
|
+
@QtCore.pyqtSlot()
|
|
924
|
+
def on_new_polygon_filter(self):
|
|
925
|
+
if not self.pipeline.slots:
|
|
926
|
+
msg = QtWidgets.QMessageBox()
|
|
927
|
+
msg.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
|
928
|
+
msg.setText("A dataset is required for creating a polygon filter!")
|
|
929
|
+
msg.setWindowTitle("No dataset loaded")
|
|
930
|
+
msg.exec()
|
|
931
|
+
else:
|
|
932
|
+
slot_index, _ = self.block_matrix.get_quickview_indices()
|
|
933
|
+
if slot_index is None:
|
|
934
|
+
# show the first dataset
|
|
935
|
+
self.on_quickview_show_dataset(0, 0)
|
|
936
|
+
self.on_quickview(view=True)
|
|
937
|
+
self.widget_quick_view.on_poly_create()
|
|
938
|
+
# adjusts QuickView size correctly
|
|
939
|
+
self.widget_quick_view.on_tool()
|
|
940
|
+
|
|
941
|
+
@widgets.show_wait_cursor
|
|
942
|
+
@QtCore.pyqtSlot(str)
|
|
943
|
+
def on_modify_filter(self, filt_id):
|
|
944
|
+
self.widget_ana_view.tabWidget.setCurrentWidget(
|
|
945
|
+
self.widget_ana_view.tab_filter)
|
|
946
|
+
self.widget_ana_view.widget_filter.show_filter(filt_id)
|
|
947
|
+
# finally, check the button
|
|
948
|
+
self.toolButton_ana_view.setChecked(True)
|
|
949
|
+
self.subwindows["analysis_view"].setVisible(True)
|
|
950
|
+
# redraw
|
|
951
|
+
self.mdiArea.update()
|
|
952
|
+
self.subwindows["analysis_view"].update()
|
|
953
|
+
|
|
954
|
+
@widgets.show_wait_cursor
|
|
955
|
+
@QtCore.pyqtSlot(str)
|
|
956
|
+
def on_modify_plot(self, plot_id):
|
|
957
|
+
self.widget_ana_view.tabWidget.setCurrentWidget(
|
|
958
|
+
self.widget_ana_view.tab_plot)
|
|
959
|
+
self.widget_ana_view.widget_plot.show_plot(plot_id)
|
|
960
|
+
# finally, check the button
|
|
961
|
+
self.toolButton_ana_view.setChecked(True)
|
|
962
|
+
self.subwindows["analysis_view"].setVisible(True)
|
|
963
|
+
# redraw
|
|
964
|
+
self.mdiArea.update()
|
|
965
|
+
self.subwindows["analysis_view"].update()
|
|
966
|
+
|
|
967
|
+
@widgets.show_wait_cursor
|
|
968
|
+
@QtCore.pyqtSlot(str)
|
|
969
|
+
def on_modify_slot(self, slot_id):
|
|
970
|
+
self.widget_ana_view.tabWidget.setCurrentWidget(
|
|
971
|
+
self.widget_ana_view.tab_slot)
|
|
972
|
+
self.widget_ana_view.widget_slot.show_slot(slot_id)
|
|
973
|
+
# finally, check the button
|
|
974
|
+
self.toolButton_ana_view.setChecked(True)
|
|
975
|
+
self.subwindows["analysis_view"].setVisible(True)
|
|
976
|
+
# redraw
|
|
977
|
+
self.mdiArea.update()
|
|
978
|
+
self.subwindows["analysis_view"].update()
|
|
979
|
+
|
|
980
|
+
@widgets.show_wait_cursor
|
|
981
|
+
@QtCore.pyqtSlot(bool)
|
|
982
|
+
def on_quickview(self, view=True):
|
|
983
|
+
"""Show/Hide QuickView (User clicked the QuickView button)"""
|
|
984
|
+
self.subwindows["quick_view"].setVisible(view)
|
|
985
|
+
self.block_matrix.enable_quickview(view)
|
|
986
|
+
# redraw
|
|
987
|
+
self.mdiArea.update()
|
|
988
|
+
if view:
|
|
989
|
+
self.subwindows["quick_view"].update()
|
|
990
|
+
|
|
991
|
+
@widgets.show_wait_cursor
|
|
992
|
+
@QtCore.pyqtSlot()
|
|
993
|
+
def on_quickview_refresh(self):
|
|
994
|
+
"""Refresh quickview with the currently shown dataset"""
|
|
995
|
+
slot_index, filt_index = self.block_matrix.get_quickview_indices()
|
|
996
|
+
if slot_index is not None:
|
|
997
|
+
self.on_quickview_show_dataset(slot_index, filt_index,
|
|
998
|
+
update_ana_filter=False)
|
|
999
|
+
|
|
1000
|
+
@widgets.show_wait_cursor
|
|
1001
|
+
@QtCore.pyqtSlot(int, int)
|
|
1002
|
+
def on_quickview_show_dataset(self,
|
|
1003
|
+
slot_index: int,
|
|
1004
|
+
filt_index: int,
|
|
1005
|
+
update_ana_filter: bool = True):
|
|
1006
|
+
"""Update QuickView dataset (User selected new dataset)
|
|
1007
|
+
|
|
1008
|
+
Parameters
|
|
1009
|
+
----------
|
|
1010
|
+
slot_index: int
|
|
1011
|
+
Index of the slot in `self.pipeline`
|
|
1012
|
+
filt_index: int
|
|
1013
|
+
Index of the filter in `self.pipeline`
|
|
1014
|
+
update_ana_filter: bool
|
|
1015
|
+
Whether to update the filter panel in the analysis view.
|
|
1016
|
+
If True, matches the filter displayed in the panel to
|
|
1017
|
+
the one where QuickView is currently set active. If
|
|
1018
|
+
False, nothing is changed.
|
|
1019
|
+
"""
|
|
1020
|
+
if slot_index < 0 or filt_index < 0:
|
|
1021
|
+
return
|
|
1022
|
+
try:
|
|
1023
|
+
ds = self.pipeline.get_dataset(slot_index=slot_index,
|
|
1024
|
+
filt_index=filt_index,
|
|
1025
|
+
apply_filter=True)
|
|
1026
|
+
except IndexError:
|
|
1027
|
+
# There is no such slot.
|
|
1028
|
+
return
|
|
1029
|
+
|
|
1030
|
+
slot = self.pipeline.slots[slot_index]
|
|
1031
|
+
# update quick view subwindow
|
|
1032
|
+
self.widget_quick_view.show_rtdc(ds, slot)
|
|
1033
|
+
# show quick view subwindow
|
|
1034
|
+
if not self.subwindows["quick_view"].isVisible():
|
|
1035
|
+
self.toolButton_quick_view.toggle()
|
|
1036
|
+
self.subwindows["quick_view"].setVisible(True)
|
|
1037
|
+
if update_ana_filter:
|
|
1038
|
+
# update FilterPanel
|
|
1039
|
+
filt_id = self.pipeline.filters[filt_index].identifier
|
|
1040
|
+
self.widget_ana_view.widget_filter.show_filter(filt_id=filt_id)
|
|
1041
|
+
|
|
1042
|
+
@QtCore.pyqtSlot()
|
|
1043
|
+
def on_splitter(self):
|
|
1044
|
+
if self.splitter.sizes()[0] == 0:
|
|
1045
|
+
self.toolButton_dm.setChecked(False)
|
|
1046
|
+
else:
|
|
1047
|
+
self.toolButton_dm.setChecked(True)
|
|
1048
|
+
|
|
1049
|
+
@QtCore.pyqtSlot()
|
|
1050
|
+
def reload_pipeline(self):
|
|
1051
|
+
"""Convenience function for reloading the current pipeline"""
|
|
1052
|
+
self.adopt_pipeline(self.pipeline.__getstate__())
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def excepthook(etype, value, trace):
|
|
1056
|
+
"""
|
|
1057
|
+
Handler for all unhandled exceptions.
|
|
1058
|
+
|
|
1059
|
+
:param `etype`: the exception type (`SyntaxError`,
|
|
1060
|
+
`ZeroDivisionError`, etc...);
|
|
1061
|
+
:type `etype`: `Exception`
|
|
1062
|
+
:param string `value`: the exception error message;
|
|
1063
|
+
:param string `trace`: the traceback header, if any (otherwise, it
|
|
1064
|
+
prints the standard Python header: ``Traceback (most recent
|
|
1065
|
+
call last)``.
|
|
1066
|
+
"""
|
|
1067
|
+
vinfo = f"Unhandled exception in DCscope version {version}:\n"
|
|
1068
|
+
tmp = traceback.format_exception(etype, value, trace)
|
|
1069
|
+
exception = "".join([vinfo]+tmp)
|
|
1070
|
+
|
|
1071
|
+
errorbox = QtWidgets.QMessageBox()
|
|
1072
|
+
errorbox.setIcon(QtWidgets.QMessageBox.Icon.Critical)
|
|
1073
|
+
errorbox.addButton(QtWidgets.QPushButton('Close'),
|
|
1074
|
+
QtWidgets.QMessageBox.ButtonRole.YesRole)
|
|
1075
|
+
errorbox.addButton(QtWidgets.QPushButton(
|
|
1076
|
+
'Copy text && Close'), QtWidgets.QMessageBox.ButtonRole.NoRole)
|
|
1077
|
+
errorbox.setText(exception)
|
|
1078
|
+
ret = errorbox.exec()
|
|
1079
|
+
print(exception)
|
|
1080
|
+
if ret == 1:
|
|
1081
|
+
cb = QtWidgets.QApplication.clipboard()
|
|
1082
|
+
cb.clear(mode=cb.Mode.Clipboard)
|
|
1083
|
+
cb.setText(exception)
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
# Make Ctr+C close the app
|
|
1087
|
+
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
1088
|
+
# Display exception hook in separate dialog instead of crashing
|
|
1089
|
+
sys.excepthook = excepthook
|