chipstream 0.2.2__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {chipstream-0.2.2 → chipstream-0.3.1}/.github/workflows/deploy_github.yml +1 -0
- chipstream-0.3.1/CHANGELOG +25 -0
- {chipstream-0.2.2/chipstream.egg-info → chipstream-0.3.1}/PKG-INFO +2 -2
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/_version.py +2 -2
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_main.py +40 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_proc.py +11 -6
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_valid.py +18 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/main_window.py +24 -16
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/main_window.ui +66 -10
- chipstream-0.3.1/chipstream/gui/table_progress.py +83 -0
- {chipstream-0.2.2 → chipstream-0.3.1/chipstream.egg-info}/PKG-INFO +2 -2
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/SOURCES.txt +2 -2
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/requires.txt +1 -1
- {chipstream-0.2.2 → chipstream-0.3.1}/pyproject.toml +1 -1
- chipstream-0.3.1/tests/test_cli.py +109 -0
- chipstream-0.3.1/tests/test_gui.py +100 -0
- chipstream-0.2.2/tests/test_manager.py → chipstream-0.3.1/tests/test_gui_manager.py +5 -5
- chipstream-0.2.2/CHANGELOG +0 -15
- chipstream-0.2.2/chipstream/gui/table_progress.py +0 -100
- chipstream-0.2.2/tests/test_cli.py +0 -40
- chipstream-0.2.2/tests/test_gui.py +0 -58
- {chipstream-0.2.2 → chipstream-0.3.1}/.github/workflows/check.yml +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/.github/workflows/deploy_pypi.yml +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/.gitignore +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/.readthedocs.yml +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/LICENSE +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/README.rst +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStream.icns +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStream.ico +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStreamLauncher.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStreamLauncherCLI.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/Readme.md +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/hook-chipstream.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/macos_ChipStream.spec +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/macos_build_app.sh +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/macos_build_requirements.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_ChipStream.spec +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_build_requirements.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_chipstream.iss_dummy +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_make_iss.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/__init__.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/__init__.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_common.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/__init__.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/img/chipstream_icon.png +0 -0
- {chipstream-0.2.2/chipstream → chipstream-0.3.1/chipstream/gui}/manager.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/splash.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/path_cache.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/dependency_links.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/entry_points.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/top_level.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/docs/artwork/chipstream_icon.svg +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/docs/artwork/chipstream_splash.png +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/docs/artwork/chipstream_splash.svg +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/docs/conf.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/docs/index.rst +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/docs/requirements.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/setup.cfg +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/tests/conftest.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/tests/data/fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/tests/helper_methods.py +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/tests/requirements-full.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/tests/requirements.txt +0 -0
- {chipstream-0.2.2 → chipstream-0.3.1}/tests/test_path_cache.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
0.3.1
|
|
2
|
+
- ref: migrate from QTableView to QTableWidget (issues with Windows 11)
|
|
3
|
+
0.3.0
|
|
4
|
+
- BREAKING CHANGE: major changes in dcnum postprocessing
|
|
5
|
+
- feat: allow to select whether volume should be computed
|
|
6
|
+
- feat: allow to select whether flickering correction should be done
|
|
7
|
+
- feat: allow to specify number of events to analyze in CLI
|
|
8
|
+
- enh: expose background computation parameters in CLI
|
|
9
|
+
- ref: move `manager` module to `gui` module
|
|
10
|
+
- setup: bump dcnum from 0.17.2 to 0.19.1
|
|
11
|
+
0.2.2
|
|
12
|
+
- maintenance release
|
|
13
|
+
0.2.1
|
|
14
|
+
- fix: cast string to paths when adding a new path to the manager (#6)
|
|
15
|
+
- setup: bump dcnum from 0.16.8 to 0.17.2
|
|
16
|
+
0.2.0
|
|
17
|
+
- feat: allow to specify an output directory in the GUI (#3)
|
|
18
|
+
- feat: allow to select the pixel size in the GUI (#4)
|
|
19
|
+
- feat: introduce PathCache for remembering previous output directories
|
|
20
|
+
- ref: support latest dcnum version
|
|
21
|
+
- ci: do not build mac app during checks
|
|
22
|
+
0.1.9
|
|
23
|
+
- initial pre-release
|
|
24
|
+
0.0.1
|
|
25
|
+
- skeleton release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: chipstream
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: GUI for DC data postprocessing
|
|
5
5
|
Author: Paul Müller
|
|
6
6
|
Maintainer-email: Paul Müller <dev@craban.de>
|
|
@@ -17,7 +17,7 @@ Classifier: Intended Audience :: Science/Research
|
|
|
17
17
|
Requires-Python: <4,>=3.10
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: dcnum>=0.
|
|
20
|
+
Requires-Dist: dcnum>=0.19.1
|
|
21
21
|
Requires-Dist: h5py<4,>=3.0.0
|
|
22
22
|
Requires-Dist: numpy<2,>=1.21
|
|
23
23
|
Provides-Extra: cli
|
|
@@ -62,6 +62,17 @@ Recursively analyze a directory containing .rtdc and .avi files::
|
|
|
62
62
|
resolve_path=True,
|
|
63
63
|
path_type=pathlib.Path),
|
|
64
64
|
)
|
|
65
|
+
@click.option("-b", "--background-method",
|
|
66
|
+
type=click.Choice(sorted(cm.bg_methods.keys()),
|
|
67
|
+
case_sensitive=False),
|
|
68
|
+
default="sparsemed",
|
|
69
|
+
help="Background computation method to use.")
|
|
70
|
+
@click.option("-kb", "background_kwargs",
|
|
71
|
+
multiple=True,
|
|
72
|
+
help="Optional ``KEY=VALUE`` argument for the specified "
|
|
73
|
+
"background method",
|
|
74
|
+
metavar="KEY=VALUE",
|
|
75
|
+
)
|
|
65
76
|
@click.option("-s", "--segmentation-method",
|
|
66
77
|
type=click.Choice(sorted(cm.seg_methods.keys()),
|
|
67
78
|
case_sensitive=False),
|
|
@@ -87,6 +98,12 @@ Recursively analyze a directory containing .rtdc and .avi files::
|
|
|
87
98
|
@click.option("-p", "--pixel-size", type=float, default=0,
|
|
88
99
|
help="Set/override the pixel size for feature "
|
|
89
100
|
"extraction [µm].")
|
|
101
|
+
@click.option("--limit-events", type=str, default="0",
|
|
102
|
+
help="Limit events of events to analyze. This can be either "
|
|
103
|
+
"a number (e.g. '5000') or a range (e.g. '5000-7000'). "
|
|
104
|
+
"You can also specify a step size (e.g. '5000-7000-2' for "
|
|
105
|
+
"every second event). The convention follows Python slices "
|
|
106
|
+
"with 'n' substituting for 'None'.")
|
|
90
107
|
@click.option("-r", "--recursive", is_flag=True,
|
|
91
108
|
help="Recurse into subdirectories.")
|
|
92
109
|
@click.option("--num-cpus",
|
|
@@ -104,11 +121,14 @@ Recursively analyze a directory containing .rtdc and .avi files::
|
|
|
104
121
|
def chipstream_cli(
|
|
105
122
|
path_in,
|
|
106
123
|
path_out=None,
|
|
124
|
+
background_method="sparsemed",
|
|
125
|
+
background_kwargs=None,
|
|
107
126
|
segmentation_method="thresh",
|
|
108
127
|
segmentation_kwargs=None,
|
|
109
128
|
feature_kwargs=None,
|
|
110
129
|
gate_kwargs=None,
|
|
111
130
|
pixel_size=0,
|
|
131
|
+
limit_events="0",
|
|
112
132
|
recursive=False,
|
|
113
133
|
num_cpus=None,
|
|
114
134
|
dry_run=False,
|
|
@@ -121,6 +141,23 @@ def chipstream_cli(
|
|
|
121
141
|
fg="yellow")
|
|
122
142
|
verbose = True
|
|
123
143
|
|
|
144
|
+
# Parse limit_frames to get the HDF5Data index_mapping
|
|
145
|
+
|
|
146
|
+
if limit_events == "0":
|
|
147
|
+
index_mapping = None
|
|
148
|
+
elif limit_events.count("-"):
|
|
149
|
+
vals = limit_events.split("-")
|
|
150
|
+
assert len(vals) in [2, 3], "slice definition must have length 2 or 3"
|
|
151
|
+
start = None if vals[0] == "n" else int(vals[0])
|
|
152
|
+
stop = None if vals[1] == "n" else int(vals[1])
|
|
153
|
+
if len(vals) == 3:
|
|
154
|
+
step = None if vals[2] == "n" else int(vals[2])
|
|
155
|
+
else:
|
|
156
|
+
step = None
|
|
157
|
+
index_mapping = slice(start, stop, step)
|
|
158
|
+
else:
|
|
159
|
+
index_mapping = int(limit_events)
|
|
160
|
+
|
|
124
161
|
# Tell the root logger to pretty-print logs
|
|
125
162
|
root_logger = logging.getLogger()
|
|
126
163
|
handler = logging.StreamHandler()
|
|
@@ -131,12 +168,15 @@ def chipstream_cli(
|
|
|
131
168
|
mp.freeze_support()
|
|
132
169
|
|
|
133
170
|
process_kwargs = dict(
|
|
171
|
+
background_method=background_method,
|
|
172
|
+
background_kwargs=background_kwargs,
|
|
134
173
|
segmentation_method=segmentation_method,
|
|
135
174
|
segmentation_kwargs=segmentation_kwargs,
|
|
136
175
|
feature_kwargs=feature_kwargs,
|
|
137
176
|
gate_kwargs=gate_kwargs,
|
|
138
177
|
pixel_size=pixel_size,
|
|
139
178
|
# Below this line are arguments that do not define the pipeline ID
|
|
179
|
+
index_mapping=index_mapping,
|
|
140
180
|
num_cpus=num_cpus or mp.cpu_count(),
|
|
141
181
|
dry_run=dry_run,
|
|
142
182
|
debug=debug,
|
|
@@ -10,20 +10,23 @@ import dcnum.read
|
|
|
10
10
|
|
|
11
11
|
from . import cli_common as cm
|
|
12
12
|
from .cli_valid import (
|
|
13
|
-
validate_feature_kwargs, validate_gate_kwargs,
|
|
14
|
-
validate_segmentation_kwargs
|
|
13
|
+
validate_background_kwargs, validate_feature_kwargs, validate_gate_kwargs,
|
|
14
|
+
validate_pixel_size, validate_segmentation_kwargs
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def process_dataset(
|
|
19
19
|
path_in: pathlib.Path,
|
|
20
20
|
path_out: pathlib.Path,
|
|
21
|
+
background_method: str,
|
|
22
|
+
background_kwargs: List[str],
|
|
21
23
|
segmentation_method: str,
|
|
22
24
|
segmentation_kwargs: List[str],
|
|
23
25
|
feature_kwargs: List[str],
|
|
24
26
|
gate_kwargs: List[str],
|
|
25
27
|
pixel_size: float,
|
|
26
28
|
# Below this line are arguments that do not affect the pipeline ID
|
|
29
|
+
index_mapping: int | slice | None,
|
|
27
30
|
num_cpus: int,
|
|
28
31
|
dry_run: bool,
|
|
29
32
|
debug: bool,
|
|
@@ -42,9 +45,10 @@ def process_dataset(
|
|
|
42
45
|
click.echo(f"Data ID:\t{dat_id}")
|
|
43
46
|
|
|
44
47
|
# background keyword arguments
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
bg_kwargs = validate_background_kwargs(background_method,
|
|
49
|
+
background_kwargs)
|
|
50
|
+
bg_cls = cm.bg_methods[background_method]
|
|
51
|
+
bg_id = bg_cls.get_ppid_from_ppkw(bg_kwargs)
|
|
48
52
|
click.echo(f"Background ID:\t{bg_id}")
|
|
49
53
|
|
|
50
54
|
# segmenter keyword arguments
|
|
@@ -84,7 +88,8 @@ def process_dataset(
|
|
|
84
88
|
path_in=path_in,
|
|
85
89
|
path_out=path_out,
|
|
86
90
|
data_code="hdf",
|
|
87
|
-
data_kwargs={"pixel_size": pixel_size
|
|
91
|
+
data_kwargs={"pixel_size": pixel_size,
|
|
92
|
+
"index_mapping": index_mapping},
|
|
88
93
|
background_code=bg_cls.get_ppid_code(),
|
|
89
94
|
background_kwargs=bg_kwargs,
|
|
90
95
|
segmenter_code=seg_cls.get_ppid_code(),
|
|
@@ -8,6 +8,24 @@ import h5py
|
|
|
8
8
|
from . import cli_common as cm
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def validate_background_kwargs(bg_method, args):
|
|
12
|
+
"""Parse background keyword arguments"""
|
|
13
|
+
# Get list of valid keyword arguments
|
|
14
|
+
bg_cls = cm.bg_methods[bg_method]
|
|
15
|
+
spec = inspect.getfullargspec(bg_cls.check_user_kwargs)
|
|
16
|
+
valid_kw = spec.kwonlyargs
|
|
17
|
+
annot = spec.annotations
|
|
18
|
+
# Convert the input args to key-value pairs
|
|
19
|
+
kwargs = {}
|
|
20
|
+
for key, value in [a.split("=") for a in args]:
|
|
21
|
+
if key not in valid_kw:
|
|
22
|
+
raise ValueError(f"Invalid keyword '{key}' for {bg_method}. "
|
|
23
|
+
+ f"Allowed keywords are {valid_kw}!")
|
|
24
|
+
# Convert to correct dtype (default to string)
|
|
25
|
+
kwargs[key] = ppid.convert_to_dtype(value, annot[key])
|
|
26
|
+
return kwargs
|
|
27
|
+
|
|
28
|
+
|
|
11
29
|
def validate_feature_kwargs(args):
|
|
12
30
|
# Get list of valid keyword arguments
|
|
13
31
|
feat_cls = cm.QueueEventExtractor
|
|
@@ -16,6 +16,7 @@ from PyQt6.QtCore import QStandardPaths
|
|
|
16
16
|
from ..path_cache import PathCache
|
|
17
17
|
from .._version import version
|
|
18
18
|
|
|
19
|
+
from .manager import ChipStreamJobManager
|
|
19
20
|
from . import splash
|
|
20
21
|
|
|
21
22
|
|
|
@@ -29,13 +30,15 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
29
30
|
application will print the version after initialization
|
|
30
31
|
and exit.
|
|
31
32
|
"""
|
|
33
|
+
self.job_manager = ChipStreamJobManager()
|
|
32
34
|
QtWidgets.QMainWindow.__init__(self)
|
|
33
35
|
ref_ui = resources.files("chipstream.gui") / "main_window.ui"
|
|
34
36
|
with resources.as_file(ref_ui) as path_ui:
|
|
35
37
|
uic.loadUi(path_ui, self)
|
|
36
38
|
|
|
39
|
+
self.tableWidget_input.set_job_manager(self.job_manager)
|
|
40
|
+
|
|
37
41
|
self.logger = logging.getLogger(__name__)
|
|
38
|
-
self.manager = self.tableView_input.model.manager
|
|
39
42
|
|
|
40
43
|
# Populate segmenter combobox
|
|
41
44
|
self.comboBox_segmenter.blockSignals(True)
|
|
@@ -92,7 +95,7 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
92
95
|
|
|
93
96
|
# Signals
|
|
94
97
|
self.run_completed.connect(self.on_run_completed)
|
|
95
|
-
self.
|
|
98
|
+
self.tableWidget_input.row_selected.connect(self.on_select_job)
|
|
96
99
|
|
|
97
100
|
# if "--version" was specified, print the version and exit
|
|
98
101
|
if "--version" in arguments:
|
|
@@ -103,7 +106,7 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
103
106
|
|
|
104
107
|
# Create a timer that continuously updates self.textBrowser
|
|
105
108
|
self.timer = QtCore.QTimer()
|
|
106
|
-
self.timer.timeout.connect(self.
|
|
109
|
+
self.timer.timeout.connect(self.tableWidget_input.on_selection_changed)
|
|
107
110
|
self.timer.start(1000)
|
|
108
111
|
|
|
109
112
|
splash.splash_close()
|
|
@@ -115,9 +118,10 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
115
118
|
|
|
116
119
|
def append_paths(self, path_list):
|
|
117
120
|
"""Add input paths to the table"""
|
|
118
|
-
if not self.
|
|
121
|
+
if not self.job_manager.is_busy():
|
|
119
122
|
for pp in path_list:
|
|
120
|
-
self.
|
|
123
|
+
self.job_manager.add_path(pp)
|
|
124
|
+
self.tableWidget_input.update_from_job_manager()
|
|
121
125
|
|
|
122
126
|
@QtCore.pyqtSlot(QtCore.QEvent)
|
|
123
127
|
def dragEnterEvent(self, e):
|
|
@@ -150,6 +154,10 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
150
154
|
|
|
151
155
|
# default background computer is "sparsemed"
|
|
152
156
|
bg_default = feat_background.BackgroundSparseMed
|
|
157
|
+
bg_kwargs = inspect.getfullargspec(
|
|
158
|
+
bg_default.check_user_kwargs).kwonlydefaults
|
|
159
|
+
bg_kwargs["offset_correction"] = \
|
|
160
|
+
self.checkBox_bg_flickering.isChecked()
|
|
153
161
|
|
|
154
162
|
# populate segmenter and its kwargs
|
|
155
163
|
segmenter = self.comboBox_segmenter.currentData()
|
|
@@ -161,14 +169,14 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
161
169
|
"data_code": "hdf",
|
|
162
170
|
"data_kwargs": data_kwargs,
|
|
163
171
|
"background_code": bg_default.get_ppid_code(),
|
|
164
|
-
"background_kwargs":
|
|
165
|
-
bg_default.check_user_kwargs).kwonlydefaults,
|
|
172
|
+
"background_kwargs": bg_kwargs,
|
|
166
173
|
"segmenter_code": segmenter,
|
|
167
174
|
"segmenter_kwargs": segmenter_kwargs,
|
|
168
175
|
"feature_code": "legacy",
|
|
169
176
|
"feature_kwargs": {
|
|
170
177
|
"brightness": self.checkBox_feat_bright.isChecked(),
|
|
171
178
|
"haralick": self.checkBox_feat_haralick.isChecked(),
|
|
179
|
+
"volume": self.checkBox_feat_volume.isChecked(),
|
|
172
180
|
},
|
|
173
181
|
"gate_code": "norm",
|
|
174
182
|
"gate_kwargs": {},
|
|
@@ -183,7 +191,7 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
183
191
|
return job_kwargs
|
|
184
192
|
|
|
185
193
|
def is_running(self):
|
|
186
|
-
return self.
|
|
194
|
+
return self.job_manager.is_busy()
|
|
187
195
|
|
|
188
196
|
@QtCore.pyqtSlot()
|
|
189
197
|
def on_action_about(self) -> None:
|
|
@@ -216,7 +224,8 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
216
224
|
@QtCore.pyqtSlot()
|
|
217
225
|
def on_action_clear(self):
|
|
218
226
|
"""Clear the current table view"""
|
|
219
|
-
self.
|
|
227
|
+
self.job_manager.clear()
|
|
228
|
+
self.tableWidget_input.update_from_job_manager()
|
|
220
229
|
|
|
221
230
|
@QtCore.pyqtSlot()
|
|
222
231
|
def on_action_docs(self):
|
|
@@ -254,7 +263,7 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
254
263
|
data = self.comboBox_output.currentData()
|
|
255
264
|
if data == "input":
|
|
256
265
|
# Store output data alongside input data
|
|
257
|
-
self.
|
|
266
|
+
self.job_manager.set_output_path(None)
|
|
258
267
|
elif data == "new":
|
|
259
268
|
# New output path
|
|
260
269
|
default = "." if len(self.path_cache) == 0 else self.path_cache[-1]
|
|
@@ -273,16 +282,15 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
273
282
|
)
|
|
274
283
|
self.comboBox_output.setCurrentIndex(len(self.path_cache) + 1)
|
|
275
284
|
self.path_cache.add_path(pathlib.Path(path))
|
|
276
|
-
self.
|
|
285
|
+
self.job_manager.set_output_path(path)
|
|
277
286
|
else:
|
|
278
287
|
# User pressed cancel
|
|
279
288
|
self.comboBox_output.setCurrentIndex(0)
|
|
280
|
-
self.
|
|
289
|
+
self.job_manager.set_output_path(None)
|
|
281
290
|
self.comboBox_output.blockSignals(False)
|
|
282
291
|
else:
|
|
283
292
|
# Data is an integer index for `self.path_cache`
|
|
284
|
-
self.
|
|
285
|
-
print(self.manager._path_out)
|
|
293
|
+
self.job_manager.set_output_path(self.path_cache[data])
|
|
286
294
|
|
|
287
295
|
@QtCore.pyqtSlot()
|
|
288
296
|
def on_run(self):
|
|
@@ -291,7 +299,7 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
291
299
|
# finished. The user can still add items to the list but not
|
|
292
300
|
# change the pipeline.
|
|
293
301
|
self.widget_options.setEnabled(False)
|
|
294
|
-
self.
|
|
302
|
+
self.job_manager.run_all_in_thread(
|
|
295
303
|
job_kwargs=self.get_job_kwargs(),
|
|
296
304
|
callback_when_done=self.run_completed.emit)
|
|
297
305
|
|
|
@@ -305,7 +313,7 @@ class ChipStream(QtWidgets.QMainWindow):
|
|
|
305
313
|
info = "No job selected."
|
|
306
314
|
else:
|
|
307
315
|
# Display some information in the lower text box.
|
|
308
|
-
info = self.
|
|
316
|
+
info = self.job_manager.get_info(row)
|
|
309
317
|
# Compare the text to the current text.
|
|
310
318
|
old_text = self.textBrowser.toPlainText()
|
|
311
319
|
if info != old_text:
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<rect>
|
|
7
7
|
<x>0</x>
|
|
8
8
|
<y>0</y>
|
|
9
|
-
<width>
|
|
10
|
-
<height>
|
|
9
|
+
<width>1120</width>
|
|
10
|
+
<height>680</height>
|
|
11
11
|
</rect>
|
|
12
12
|
</property>
|
|
13
13
|
<property name="windowTitle">
|
|
@@ -41,15 +41,18 @@
|
|
|
41
41
|
<property name="orientation">
|
|
42
42
|
<enum>Qt::Vertical</enum>
|
|
43
43
|
</property>
|
|
44
|
-
<widget class="ProgressTable" name="
|
|
44
|
+
<widget class="ProgressTable" name="tableWidget_input">
|
|
45
45
|
<property name="sizePolicy">
|
|
46
46
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
47
47
|
<horstretch>1</horstretch>
|
|
48
48
|
<verstretch>3</verstretch>
|
|
49
49
|
</sizepolicy>
|
|
50
50
|
</property>
|
|
51
|
+
<property name="editTriggers">
|
|
52
|
+
<set>QAbstractItemView::NoEditTriggers</set>
|
|
53
|
+
</property>
|
|
51
54
|
<property name="dragDropMode">
|
|
52
|
-
<enum>QAbstractItemView::
|
|
55
|
+
<enum>QAbstractItemView::NoDragDrop</enum>
|
|
53
56
|
</property>
|
|
54
57
|
<property name="alternatingRowColors">
|
|
55
58
|
<bool>true</bool>
|
|
@@ -60,6 +63,12 @@
|
|
|
60
63
|
<property name="selectionBehavior">
|
|
61
64
|
<enum>QAbstractItemView::SelectRows</enum>
|
|
62
65
|
</property>
|
|
66
|
+
<property name="textElideMode">
|
|
67
|
+
<enum>Qt::ElideMiddle</enum>
|
|
68
|
+
</property>
|
|
69
|
+
<property name="horizontalScrollMode">
|
|
70
|
+
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
|
71
|
+
</property>
|
|
63
72
|
<property name="sortingEnabled">
|
|
64
73
|
<bool>true</bool>
|
|
65
74
|
</property>
|
|
@@ -67,14 +76,32 @@
|
|
|
67
76
|
<bool>false</bool>
|
|
68
77
|
</property>
|
|
69
78
|
<attribute name="horizontalHeaderMinimumSectionSize">
|
|
70
|
-
<number>
|
|
79
|
+
<number>100</number>
|
|
71
80
|
</attribute>
|
|
72
|
-
<attribute name="
|
|
73
|
-
<
|
|
81
|
+
<attribute name="horizontalHeaderDefaultSectionSize">
|
|
82
|
+
<number>100</number>
|
|
74
83
|
</attribute>
|
|
75
|
-
<attribute name="
|
|
84
|
+
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
|
76
85
|
<bool>false</bool>
|
|
77
86
|
</attribute>
|
|
87
|
+
<attribute name="horizontalHeaderStretchLastSection">
|
|
88
|
+
<bool>true</bool>
|
|
89
|
+
</attribute>
|
|
90
|
+
<column>
|
|
91
|
+
<property name="text">
|
|
92
|
+
<string>Path</string>
|
|
93
|
+
</property>
|
|
94
|
+
</column>
|
|
95
|
+
<column>
|
|
96
|
+
<property name="text">
|
|
97
|
+
<string>State</string>
|
|
98
|
+
</property>
|
|
99
|
+
</column>
|
|
100
|
+
<column>
|
|
101
|
+
<property name="text">
|
|
102
|
+
<string>Progress</string>
|
|
103
|
+
</property>
|
|
104
|
+
</column>
|
|
78
105
|
</widget>
|
|
79
106
|
<widget class="QTextBrowser" name="textBrowser">
|
|
80
107
|
<property name="sizePolicy">
|
|
@@ -122,6 +149,25 @@
|
|
|
122
149
|
</property>
|
|
123
150
|
</spacer>
|
|
124
151
|
</item>
|
|
152
|
+
<item>
|
|
153
|
+
<widget class="QGroupBox" name="groupBox">
|
|
154
|
+
<property name="title">
|
|
155
|
+
<string>Background Image</string>
|
|
156
|
+
</property>
|
|
157
|
+
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
158
|
+
<item>
|
|
159
|
+
<widget class="QCheckBox" name="checkBox_bg_flickering">
|
|
160
|
+
<property name="text">
|
|
161
|
+
<string>flickering correction</string>
|
|
162
|
+
</property>
|
|
163
|
+
<property name="checked">
|
|
164
|
+
<bool>true</bool>
|
|
165
|
+
</property>
|
|
166
|
+
</widget>
|
|
167
|
+
</item>
|
|
168
|
+
</layout>
|
|
169
|
+
</widget>
|
|
170
|
+
</item>
|
|
125
171
|
<item>
|
|
126
172
|
<widget class="QGroupBox" name="groupBox_4">
|
|
127
173
|
<property name="sizePolicy">
|
|
@@ -233,6 +279,16 @@
|
|
|
233
279
|
</property>
|
|
234
280
|
</widget>
|
|
235
281
|
</item>
|
|
282
|
+
<item>
|
|
283
|
+
<widget class="QCheckBox" name="checkBox_feat_volume">
|
|
284
|
+
<property name="text">
|
|
285
|
+
<string>Volume</string>
|
|
286
|
+
</property>
|
|
287
|
+
<property name="checked">
|
|
288
|
+
<bool>true</bool>
|
|
289
|
+
</property>
|
|
290
|
+
</widget>
|
|
291
|
+
</item>
|
|
236
292
|
<item>
|
|
237
293
|
<widget class="QCheckBox" name="checkBox_feat_haralick">
|
|
238
294
|
<property name="text">
|
|
@@ -335,7 +391,7 @@
|
|
|
335
391
|
<rect>
|
|
336
392
|
<x>0</x>
|
|
337
393
|
<y>0</y>
|
|
338
|
-
<width>
|
|
394
|
+
<width>1120</width>
|
|
339
395
|
<height>22</height>
|
|
340
396
|
</rect>
|
|
341
397
|
</property>
|
|
@@ -399,7 +455,7 @@
|
|
|
399
455
|
<customwidgets>
|
|
400
456
|
<customwidget>
|
|
401
457
|
<class>ProgressTable</class>
|
|
402
|
-
<extends>
|
|
458
|
+
<extends>QTableWidget</extends>
|
|
403
459
|
<header>chipstream.gui.table_progress</header>
|
|
404
460
|
</customwidget>
|
|
405
461
|
</customwidgets>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from PyQt6 import QtCore, QtWidgets
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ProgressTable(QtWidgets.QTableWidget):
|
|
5
|
+
row_selected = QtCore.pyqtSignal(int)
|
|
6
|
+
|
|
7
|
+
def __init__(self, *args, **kwargs):
|
|
8
|
+
super(ProgressTable, self).__init__(*args, **kwargs)
|
|
9
|
+
self.job_manager = None
|
|
10
|
+
|
|
11
|
+
# signals
|
|
12
|
+
self.itemSelectionChanged.connect(
|
|
13
|
+
self.on_selection_changed)
|
|
14
|
+
|
|
15
|
+
# timer for updating table contents
|
|
16
|
+
self.monitor_timer = QtCore.QTimer(self)
|
|
17
|
+
self.monitor_timer.timeout.connect(
|
|
18
|
+
self.update_from_job_manager_progress)
|
|
19
|
+
self.monitor_timer.start(300)
|
|
20
|
+
|
|
21
|
+
@QtCore.pyqtSlot()
|
|
22
|
+
def on_selection_changed(self):
|
|
23
|
+
"""Emit a row-selected signal"""
|
|
24
|
+
row = self.currentIndex().row()
|
|
25
|
+
self.row_selected.emit(row)
|
|
26
|
+
|
|
27
|
+
def set_job_manager(self, job_manager):
|
|
28
|
+
if self.job_manager is None:
|
|
29
|
+
self.job_manager = job_manager
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError("Job manager already set!")
|
|
32
|
+
|
|
33
|
+
def set_item_label(self, row, col, label, align=None):
|
|
34
|
+
"""Get/Create a Qlabel at the specified position
|
|
35
|
+
"""
|
|
36
|
+
label = f"{label}"
|
|
37
|
+
item = self.item(row, col)
|
|
38
|
+
if item is None:
|
|
39
|
+
item = QtWidgets.QTableWidgetItem(label)
|
|
40
|
+
self.setItem(row, col, item)
|
|
41
|
+
if align is not None:
|
|
42
|
+
item.setTextAlignment(align)
|
|
43
|
+
else:
|
|
44
|
+
if item.text() != label:
|
|
45
|
+
item.setText(label)
|
|
46
|
+
|
|
47
|
+
def set_item_progress(self, row, col, progress):
|
|
48
|
+
"""Get/Create a QProgressBar at the specified position
|
|
49
|
+
"""
|
|
50
|
+
pb = self.cellWidget(row, col)
|
|
51
|
+
if pb is None:
|
|
52
|
+
pb = QtWidgets.QProgressBar(self)
|
|
53
|
+
pb.setMaximum(1000)
|
|
54
|
+
self.setCellWidget(row, col, pb)
|
|
55
|
+
else:
|
|
56
|
+
if pb.value() != int(progress*1000):
|
|
57
|
+
pb.setValue(int(progress*1000))
|
|
58
|
+
|
|
59
|
+
@QtCore.pyqtSlot()
|
|
60
|
+
def update_from_job_manager(self):
|
|
61
|
+
if self.job_manager is None:
|
|
62
|
+
raise ValueError("Job manager not set!")
|
|
63
|
+
self.setRowCount(len(self.job_manager))
|
|
64
|
+
# Check rows and populate new items
|
|
65
|
+
for ii in range(len(self.job_manager)):
|
|
66
|
+
status = self.job_manager[ii]
|
|
67
|
+
self.set_item_label(ii, 0, str(status["path"]))
|
|
68
|
+
self.set_item_label(ii, 1, str(status["state"]),
|
|
69
|
+
align=QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
70
|
+
self.set_item_progress(ii, 2, status["progress"])
|
|
71
|
+
# Set path column width to something large (does not work during init)
|
|
72
|
+
if self.columnWidth(0) == 100:
|
|
73
|
+
self.setColumnWidth(0, 500)
|
|
74
|
+
|
|
75
|
+
@QtCore.pyqtSlot()
|
|
76
|
+
def update_from_job_manager_progress(self):
|
|
77
|
+
for ii in range(len(self.job_manager)):
|
|
78
|
+
st = self.item(ii, 1)
|
|
79
|
+
st.setText(self.job_manager[ii]["state"])
|
|
80
|
+
pb = self.cellWidget(ii, 2)
|
|
81
|
+
progress = self.job_manager[ii]["progress"]
|
|
82
|
+
if pb is not None and pb.value() != int(progress*1000):
|
|
83
|
+
pb.setValue(int(progress*1000))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: chipstream
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: GUI for DC data postprocessing
|
|
5
5
|
Author: Paul Müller
|
|
6
6
|
Maintainer-email: Paul Müller <dev@craban.de>
|
|
@@ -17,7 +17,7 @@ Classifier: Intended Audience :: Science/Research
|
|
|
17
17
|
Requires-Python: <4,>=3.10
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: dcnum>=0.
|
|
20
|
+
Requires-Dist: dcnum>=0.19.1
|
|
21
21
|
Requires-Dist: h5py<4,>=3.0.0
|
|
22
22
|
Requires-Dist: numpy<2,>=1.21
|
|
23
23
|
Provides-Extra: cli
|
|
@@ -22,7 +22,6 @@ build-recipes/win_chipstream.iss_dummy
|
|
|
22
22
|
build-recipes/win_make_iss.py
|
|
23
23
|
chipstream/__init__.py
|
|
24
24
|
chipstream/_version.py
|
|
25
|
-
chipstream/manager.py
|
|
26
25
|
chipstream/path_cache.py
|
|
27
26
|
chipstream.egg-info/PKG-INFO
|
|
28
27
|
chipstream.egg-info/SOURCES.txt
|
|
@@ -38,6 +37,7 @@ chipstream/cli/cli_valid.py
|
|
|
38
37
|
chipstream/gui/__init__.py
|
|
39
38
|
chipstream/gui/main_window.py
|
|
40
39
|
chipstream/gui/main_window.ui
|
|
40
|
+
chipstream/gui/manager.py
|
|
41
41
|
chipstream/gui/splash.py
|
|
42
42
|
chipstream/gui/table_progress.py
|
|
43
43
|
chipstream/gui/img/chipstream_icon.png
|
|
@@ -53,6 +53,6 @@ tests/requirements-full.txt
|
|
|
53
53
|
tests/requirements.txt
|
|
54
54
|
tests/test_cli.py
|
|
55
55
|
tests/test_gui.py
|
|
56
|
-
tests/
|
|
56
|
+
tests/test_gui_manager.py
|
|
57
57
|
tests/test_path_cache.py
|
|
58
58
|
tests/data/fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import dcnum.read
|
|
2
|
+
import h5py
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from helper_methods import retrieve_data
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
pytest.importorskip("click")
|
|
10
|
+
|
|
11
|
+
from chipstream.cli import cli_main # noqa: E402
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize("limit_events,dcnum_mapping,dcnum_yield,f0", [
|
|
15
|
+
# this is the default
|
|
16
|
+
["0", "0", 36, 1],
|
|
17
|
+
["1-4", "1-4-n", 6, 2],
|
|
18
|
+
["3-10-5", "3-10-5", 5, 4],
|
|
19
|
+
])
|
|
20
|
+
def test_cli_limit_events(cli_runner, limit_events, dcnum_yield,
|
|
21
|
+
dcnum_mapping, f0):
|
|
22
|
+
path_temp = retrieve_data(
|
|
23
|
+
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
24
|
+
path = path_temp.with_name("input_path.rtdc")
|
|
25
|
+
|
|
26
|
+
# create a test file for more than 100 events
|
|
27
|
+
with dcnum.read.concatenated_hdf5_data(
|
|
28
|
+
paths=3*[path_temp],
|
|
29
|
+
path_out=path,
|
|
30
|
+
compute_frame=True):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
# sanity check
|
|
34
|
+
with h5py.File(path) as h5:
|
|
35
|
+
assert h5["events/frame"][0] == 1
|
|
36
|
+
assert h5["events/frame"][1] == 2
|
|
37
|
+
|
|
38
|
+
path_out = path.with_name("limited_events.rtdc")
|
|
39
|
+
result = cli_runner.invoke(cli_main.chipstream_cli,
|
|
40
|
+
[str(path),
|
|
41
|
+
str(path_out),
|
|
42
|
+
"-s", "thresh",
|
|
43
|
+
"--limit-events", limit_events,
|
|
44
|
+
])
|
|
45
|
+
assert result.exit_code == 0
|
|
46
|
+
|
|
47
|
+
with h5py.File(path_out) as h5:
|
|
48
|
+
assert h5["events/frame"][0] == f0
|
|
49
|
+
assert h5.attrs["pipeline:dcnum yield"] == dcnum_yield
|
|
50
|
+
assert h5.attrs["pipeline:dcnum mapping"] == dcnum_mapping
|
|
51
|
+
assert h5.attrs["experiment:event count"] == dcnum_yield
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_cli_set_pixel_size(cli_runner):
|
|
55
|
+
path_temp = retrieve_data(
|
|
56
|
+
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
57
|
+
path = path_temp.with_name("input_path.rtdc")
|
|
58
|
+
|
|
59
|
+
# create a test file for more than 100 events
|
|
60
|
+
with dcnum.read.concatenated_hdf5_data(
|
|
61
|
+
paths=3*[path_temp],
|
|
62
|
+
path_out=path,
|
|
63
|
+
compute_frame=True):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# remove the pixel size from the input data
|
|
67
|
+
with h5py.File(path, "a") as h5:
|
|
68
|
+
del h5.attrs["imaging:pixel size"]
|
|
69
|
+
|
|
70
|
+
path_out = path.with_name("with_pixel_size_dcn.rtdc")
|
|
71
|
+
result = cli_runner.invoke(cli_main.chipstream_cli,
|
|
72
|
+
[str(path),
|
|
73
|
+
str(path_out),
|
|
74
|
+
"-s", "thresh",
|
|
75
|
+
"-p", "0.266",
|
|
76
|
+
])
|
|
77
|
+
assert result.exit_code == 0
|
|
78
|
+
|
|
79
|
+
with h5py.File(path_out) as h5:
|
|
80
|
+
assert h5.attrs["imaging:pixel size"] == 0.266
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.parametrize("method,kwarg,ppid", [
|
|
84
|
+
["sparsemed", "offset_correction=0", "sparsemed:k=200^s=1^t=0^f=0.8^o=0"],
|
|
85
|
+
["rollmed", "kernel_size=12", "rollmed:k=12^b=10000"],
|
|
86
|
+
])
|
|
87
|
+
def test_cli_set_background(cli_runner, method, kwarg, ppid):
|
|
88
|
+
path_temp = retrieve_data(
|
|
89
|
+
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
90
|
+
path = path_temp.with_name("input_path.rtdc")
|
|
91
|
+
|
|
92
|
+
# create a test file for more than 100 events
|
|
93
|
+
with dcnum.read.concatenated_hdf5_data(
|
|
94
|
+
paths=3*[path_temp],
|
|
95
|
+
path_out=path,
|
|
96
|
+
compute_frame=True):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
path_out = path.with_name("output.rtdc")
|
|
100
|
+
result = cli_runner.invoke(cli_main.chipstream_cli,
|
|
101
|
+
[str(path),
|
|
102
|
+
str(path_out),
|
|
103
|
+
"-b", method,
|
|
104
|
+
"-kb", kwarg,
|
|
105
|
+
])
|
|
106
|
+
assert result.exit_code == 0
|
|
107
|
+
|
|
108
|
+
with h5py.File(path_out) as h5:
|
|
109
|
+
assert h5.attrs["pipeline:dcnum background"] == ppid
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import h5py
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from helper_methods import retrieve_data
|
|
8
|
+
|
|
9
|
+
pytest.importorskip("PyQt6")
|
|
10
|
+
|
|
11
|
+
from PyQt6 import QtCore, QtWidgets, QtTest # noqa: E402
|
|
12
|
+
|
|
13
|
+
from chipstream.gui.main_window import ChipStream # noqa: E402
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def mw(qtbot):
|
|
18
|
+
# Code that will run before your test
|
|
19
|
+
mw = ChipStream()
|
|
20
|
+
qtbot.addWidget(mw)
|
|
21
|
+
QtWidgets.QApplication.setActiveWindow(mw)
|
|
22
|
+
QtTest.QTest.qWait(100)
|
|
23
|
+
QtWidgets.QApplication.processEvents(
|
|
24
|
+
QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
|
|
25
|
+
# Run test
|
|
26
|
+
yield mw
|
|
27
|
+
# Make sure that all daemons are gone
|
|
28
|
+
mw.close()
|
|
29
|
+
# It is extremely weird, but this seems to be important to avoid segfaults!
|
|
30
|
+
time.sleep(1)
|
|
31
|
+
QtTest.QTest.qWait(100)
|
|
32
|
+
QtWidgets.QApplication.processEvents(
|
|
33
|
+
QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_gui_basic(mw):
|
|
37
|
+
# Just check some known properties in the UI.
|
|
38
|
+
assert mw.spinBox_thresh.value() == -6
|
|
39
|
+
assert mw.checkBox_feat_bright.isChecked()
|
|
40
|
+
assert len(mw.job_manager) == 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.parametrize("correct_offset", [True, False])
|
|
44
|
+
def test_gui_correct_offset(mw, correct_offset):
|
|
45
|
+
path = retrieve_data(
|
|
46
|
+
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
47
|
+
mw.append_paths([path])
|
|
48
|
+
mw.checkBox_pixel_size.setChecked(True)
|
|
49
|
+
mw.checkBox_bg_flickering.setChecked(correct_offset)
|
|
50
|
+
mw.doubleSpinBox_pixel_size.setValue(0.666)
|
|
51
|
+
mw.on_run()
|
|
52
|
+
while mw.job_manager.is_busy():
|
|
53
|
+
time.sleep(.1)
|
|
54
|
+
out_path = path.with_name(path.stem + "_dcn.rtdc")
|
|
55
|
+
assert out_path.exists()
|
|
56
|
+
|
|
57
|
+
with h5py.File(out_path) as h5:
|
|
58
|
+
assert np.allclose(h5.attrs["imaging:pixel size"],
|
|
59
|
+
0.666,
|
|
60
|
+
atol=0, rtol=1e-5)
|
|
61
|
+
assert ("bg_off" in h5["events"]) == correct_offset
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_gui_set_pixel_size(mw):
|
|
65
|
+
path = retrieve_data(
|
|
66
|
+
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
67
|
+
mw.append_paths([path])
|
|
68
|
+
mw.checkBox_pixel_size.setChecked(True)
|
|
69
|
+
mw.doubleSpinBox_pixel_size.setValue(0.666)
|
|
70
|
+
mw.on_run()
|
|
71
|
+
while mw.job_manager.is_busy():
|
|
72
|
+
time.sleep(.1)
|
|
73
|
+
out_path = path.with_name(path.stem + "_dcn.rtdc")
|
|
74
|
+
assert out_path.exists()
|
|
75
|
+
|
|
76
|
+
with h5py.File(out_path) as h5:
|
|
77
|
+
assert np.allclose(h5.attrs["imaging:pixel size"],
|
|
78
|
+
0.666,
|
|
79
|
+
atol=0, rtol=1e-5)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.parametrize("use_volume", [True, False])
|
|
83
|
+
def test_gui_use_volume(mw, use_volume):
|
|
84
|
+
path = retrieve_data(
|
|
85
|
+
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
86
|
+
mw.append_paths([path])
|
|
87
|
+
mw.checkBox_pixel_size.setChecked(True)
|
|
88
|
+
mw.checkBox_feat_volume.setChecked(use_volume)
|
|
89
|
+
mw.doubleSpinBox_pixel_size.setValue(0.666)
|
|
90
|
+
mw.on_run()
|
|
91
|
+
while mw.job_manager.is_busy():
|
|
92
|
+
time.sleep(.1)
|
|
93
|
+
out_path = path.with_name(path.stem + "_dcn.rtdc")
|
|
94
|
+
assert out_path.exists()
|
|
95
|
+
|
|
96
|
+
with h5py.File(out_path) as h5:
|
|
97
|
+
assert np.allclose(h5.attrs["imaging:pixel size"],
|
|
98
|
+
0.666,
|
|
99
|
+
atol=0, rtol=1e-5)
|
|
100
|
+
assert ("volume" in h5["events"]) == use_volume
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import shutil
|
|
2
2
|
|
|
3
|
-
from chipstream import manager
|
|
3
|
+
from chipstream.gui import manager
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
from helper_methods import retrieve_data
|
|
@@ -80,11 +80,11 @@ def test_manager_run_defaults():
|
|
|
80
80
|
assert mg.current_index == 0
|
|
81
81
|
assert not mg.is_busy()
|
|
82
82
|
# default pipeline may change in dcnum
|
|
83
|
-
assert mg.get_runner(0).ppid == ("
|
|
84
|
-
"hdf:p=0.2645|"
|
|
85
|
-
"sparsemed:k=200^s=1^t=0^f=0.8|"
|
|
83
|
+
assert mg.get_runner(0).ppid == ("8|"
|
|
84
|
+
"hdf:p=0.2645^i=0|"
|
|
85
|
+
"sparsemed:k=200^s=1^t=0^f=0.8^o=1|"
|
|
86
86
|
"thresh:t=-6:cle=1^f=1^clo=2|"
|
|
87
|
-
"legacy:b=1^h=1|"
|
|
87
|
+
"legacy:b=1^h=1^v=1|"
|
|
88
88
|
"norm:o=0^s=10")
|
|
89
89
|
|
|
90
90
|
|
chipstream-0.2.2/CHANGELOG
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
0.2.2
|
|
2
|
-
- maintenance release
|
|
3
|
-
0.2.1
|
|
4
|
-
- fix: cast string to paths when adding a new path to the manager (#6)
|
|
5
|
-
- setup: bump dcnum from 0.16.8 to 0 17.2
|
|
6
|
-
0.2.0
|
|
7
|
-
- feat: allow to specify an output directory in the GUI (#3)
|
|
8
|
-
- feat: allow to select the pixel size in the GUI (#4)
|
|
9
|
-
- feat: introduce PathCache for remembering previous output directories
|
|
10
|
-
- ref: support latest dcnum version
|
|
11
|
-
- ci: do not build mac app during checks
|
|
12
|
-
0.1.9
|
|
13
|
-
- initial pre-release
|
|
14
|
-
0.0.1
|
|
15
|
-
- skeleton release
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
from PyQt6 import QtCore, QtWidgets
|
|
2
|
-
|
|
3
|
-
from ..manager import ChipStreamJobManager
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ItemProgressRole = QtCore.Qt.ItemDataRole.UserRole + 1001
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ProgressDelegate(QtWidgets.QStyledItemDelegate):
|
|
10
|
-
def paint(self, painter, option, index):
|
|
11
|
-
progress = index.data(ItemProgressRole)
|
|
12
|
-
opt = QtWidgets.QStyleOptionProgressBar()
|
|
13
|
-
opt.rect = option.rect
|
|
14
|
-
opt.minimum = 0
|
|
15
|
-
opt.maximum = 100
|
|
16
|
-
opt.progress = int(progress * 100)
|
|
17
|
-
opt.text = f"{progress:.1%}"
|
|
18
|
-
opt.textVisible = True
|
|
19
|
-
QtWidgets.QApplication.style().drawControl(
|
|
20
|
-
QtWidgets.QStyle.ControlElement.CE_ProgressBar, opt, painter)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class ProgressModel(QtCore.QAbstractTableModel):
|
|
24
|
-
def __init__(self, *args, **kwargs):
|
|
25
|
-
super(ProgressModel, self).__init__(*args, **kwargs)
|
|
26
|
-
self.manager = ChipStreamJobManager()
|
|
27
|
-
self.map_columns = ["path", "state", "progress"]
|
|
28
|
-
self.headers = [m.capitalize() for m in self.map_columns]
|
|
29
|
-
self.monitor_timer = QtCore.QTimer(self)
|
|
30
|
-
self.monitor_timer.timeout.connect(self.monitor_current_job)
|
|
31
|
-
self.monitor_timer.start(300)
|
|
32
|
-
|
|
33
|
-
def add_input_path(self, path):
|
|
34
|
-
self.manager.add_path(path)
|
|
35
|
-
self.layoutChanged.emit()
|
|
36
|
-
|
|
37
|
-
def data(self, index, role):
|
|
38
|
-
status = self.manager[index.row()]
|
|
39
|
-
key = self.map_columns[index.column()]
|
|
40
|
-
if role in [ItemProgressRole, QtCore.Qt.ItemDataRole.DisplayRole]:
|
|
41
|
-
return status[key]
|
|
42
|
-
else:
|
|
43
|
-
return QtCore.QVariant()
|
|
44
|
-
|
|
45
|
-
def columnCount(self, parent):
|
|
46
|
-
return len(self.headers)
|
|
47
|
-
|
|
48
|
-
def headerData(self, section, orientation, role):
|
|
49
|
-
if role != QtCore.Qt.ItemDataRole.DisplayRole:
|
|
50
|
-
return QtCore.QVariant()
|
|
51
|
-
return self.headers[section]
|
|
52
|
-
|
|
53
|
-
def rowCount(self, parent):
|
|
54
|
-
return len(self.manager)
|
|
55
|
-
|
|
56
|
-
@QtCore.pyqtSlot()
|
|
57
|
-
def monitor_current_job(self):
|
|
58
|
-
current_index = self.manager.current_index
|
|
59
|
-
if current_index is not None:
|
|
60
|
-
self.update_index(current_index)
|
|
61
|
-
|
|
62
|
-
@QtCore.pyqtSlot(int)
|
|
63
|
-
def update_index(self, index):
|
|
64
|
-
# update the row
|
|
65
|
-
index_1 = self.index(index, 0)
|
|
66
|
-
index_2 = self.index(index, len(self.headers))
|
|
67
|
-
self.dataChanged.emit(index_1,
|
|
68
|
-
index_2,
|
|
69
|
-
[QtCore.Qt.ItemDataRole.DisplayRole,
|
|
70
|
-
QtCore.Qt.ItemDataRole.DisplayRole,
|
|
71
|
-
ItemProgressRole])
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class ProgressTable(QtWidgets.QTableView):
|
|
75
|
-
row_selected = QtCore.pyqtSignal(int)
|
|
76
|
-
|
|
77
|
-
def __init__(self, *args, **kwargs):
|
|
78
|
-
super(ProgressTable, self).__init__(*args, **kwargs)
|
|
79
|
-
pbar_delegate = ProgressDelegate(self)
|
|
80
|
-
self.setItemDelegateForColumn(2, pbar_delegate)
|
|
81
|
-
self.model = ProgressModel()
|
|
82
|
-
self.setModel(self.model)
|
|
83
|
-
self.setColumnWidth(0, 400)
|
|
84
|
-
self.setColumnWidth(1, 80)
|
|
85
|
-
|
|
86
|
-
# signals
|
|
87
|
-
self.selectionModel().selectionChanged.connect(
|
|
88
|
-
self.on_selection_changed)
|
|
89
|
-
|
|
90
|
-
def add_input_path(self, path):
|
|
91
|
-
self.model.add_input_path(path)
|
|
92
|
-
|
|
93
|
-
@QtCore.pyqtSlot()
|
|
94
|
-
def on_selection_changed(self):
|
|
95
|
-
"""Emit a row-selected signal"""
|
|
96
|
-
row = self.selectionModel().currentIndex().row()
|
|
97
|
-
self.row_selected.emit(row)
|
|
98
|
-
|
|
99
|
-
def update(self):
|
|
100
|
-
self.model.dataChanged.emit()
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import dcnum.read
|
|
2
|
-
import h5py
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
|
|
6
|
-
from helper_methods import retrieve_data
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
pytest.importorskip("click")
|
|
10
|
-
|
|
11
|
-
from chipstream.cli import cli_main # noqa: E402
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_cli_set_pixel_size(cli_runner):
|
|
15
|
-
path_temp = retrieve_data(
|
|
16
|
-
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
17
|
-
path = path_temp.with_name("input_path.rtdc")
|
|
18
|
-
|
|
19
|
-
# create a test file for more than 100 events
|
|
20
|
-
with dcnum.read.concatenated_hdf5_data(
|
|
21
|
-
paths=3*[path_temp],
|
|
22
|
-
path_out=path,
|
|
23
|
-
compute_frame=True):
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
# remove the pixel size from the input data
|
|
27
|
-
with h5py.File(path, "a") as h5:
|
|
28
|
-
del h5.attrs["imaging:pixel size"]
|
|
29
|
-
|
|
30
|
-
path_out = path.with_name("with_pixel_size_dcn.rtdc")
|
|
31
|
-
result = cli_runner.invoke(cli_main.chipstream_cli,
|
|
32
|
-
[str(path),
|
|
33
|
-
str(path_out),
|
|
34
|
-
"-s", "thresh",
|
|
35
|
-
"-p", "0.266",
|
|
36
|
-
])
|
|
37
|
-
assert result.exit_code == 0
|
|
38
|
-
|
|
39
|
-
with h5py.File(path_out) as h5:
|
|
40
|
-
assert h5.attrs["imaging:pixel size"] == 0.266
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
|
|
3
|
-
import h5py
|
|
4
|
-
import numpy as np
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from helper_methods import retrieve_data
|
|
8
|
-
|
|
9
|
-
pytest.importorskip("PyQt6")
|
|
10
|
-
|
|
11
|
-
from PyQt6 import QtCore, QtWidgets, QtTest # noqa: E402
|
|
12
|
-
|
|
13
|
-
from chipstream.gui.main_window import ChipStream # noqa: E402
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@pytest.fixture
|
|
17
|
-
def mw(qtbot):
|
|
18
|
-
# Code that will run before your test
|
|
19
|
-
mw = ChipStream()
|
|
20
|
-
qtbot.addWidget(mw)
|
|
21
|
-
QtWidgets.QApplication.setActiveWindow(mw)
|
|
22
|
-
QtTest.QTest.qWait(100)
|
|
23
|
-
QtWidgets.QApplication.processEvents(
|
|
24
|
-
QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
|
|
25
|
-
# Run test
|
|
26
|
-
yield mw
|
|
27
|
-
# Make sure that all daemons are gone
|
|
28
|
-
mw.close()
|
|
29
|
-
# It is extremely weird, but this seems to be important to avoid segfaults!
|
|
30
|
-
time.sleep(1)
|
|
31
|
-
QtTest.QTest.qWait(100)
|
|
32
|
-
QtWidgets.QApplication.processEvents(
|
|
33
|
-
QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_gui_basic(mw):
|
|
37
|
-
# Just check some known properties in the UI.
|
|
38
|
-
assert mw.spinBox_thresh.value() == -6
|
|
39
|
-
assert mw.checkBox_feat_bright.isChecked()
|
|
40
|
-
assert len(mw.manager) == 0
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_gui_set_pixel_size(mw):
|
|
44
|
-
path = retrieve_data(
|
|
45
|
-
"fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip")
|
|
46
|
-
mw.append_paths([path])
|
|
47
|
-
mw.checkBox_pixel_size.setChecked(True)
|
|
48
|
-
mw.doubleSpinBox_pixel_size.setValue(0.666)
|
|
49
|
-
mw.on_run()
|
|
50
|
-
while mw.manager.is_busy():
|
|
51
|
-
time.sleep(.1)
|
|
52
|
-
out_path = path.with_name(path.stem + "_dcn.rtdc")
|
|
53
|
-
assert out_path.exists()
|
|
54
|
-
|
|
55
|
-
with h5py.File(out_path) as h5:
|
|
56
|
-
assert np.allclose(h5.attrs["imaging:pixel size"],
|
|
57
|
-
0.666,
|
|
58
|
-
atol=0, rtol=1e-5)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|