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.
Files changed (64) hide show
  1. {chipstream-0.2.2 → chipstream-0.3.1}/.github/workflows/deploy_github.yml +1 -0
  2. chipstream-0.3.1/CHANGELOG +25 -0
  3. {chipstream-0.2.2/chipstream.egg-info → chipstream-0.3.1}/PKG-INFO +2 -2
  4. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/_version.py +2 -2
  5. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_main.py +40 -0
  6. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_proc.py +11 -6
  7. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_valid.py +18 -0
  8. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/main_window.py +24 -16
  9. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/main_window.ui +66 -10
  10. chipstream-0.3.1/chipstream/gui/table_progress.py +83 -0
  11. {chipstream-0.2.2 → chipstream-0.3.1/chipstream.egg-info}/PKG-INFO +2 -2
  12. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/SOURCES.txt +2 -2
  13. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/requires.txt +1 -1
  14. {chipstream-0.2.2 → chipstream-0.3.1}/pyproject.toml +1 -1
  15. chipstream-0.3.1/tests/test_cli.py +109 -0
  16. chipstream-0.3.1/tests/test_gui.py +100 -0
  17. chipstream-0.2.2/tests/test_manager.py → chipstream-0.3.1/tests/test_gui_manager.py +5 -5
  18. chipstream-0.2.2/CHANGELOG +0 -15
  19. chipstream-0.2.2/chipstream/gui/table_progress.py +0 -100
  20. chipstream-0.2.2/tests/test_cli.py +0 -40
  21. chipstream-0.2.2/tests/test_gui.py +0 -58
  22. {chipstream-0.2.2 → chipstream-0.3.1}/.github/workflows/check.yml +0 -0
  23. {chipstream-0.2.2 → chipstream-0.3.1}/.github/workflows/deploy_pypi.yml +0 -0
  24. {chipstream-0.2.2 → chipstream-0.3.1}/.gitignore +0 -0
  25. {chipstream-0.2.2 → chipstream-0.3.1}/.readthedocs.yml +0 -0
  26. {chipstream-0.2.2 → chipstream-0.3.1}/LICENSE +0 -0
  27. {chipstream-0.2.2 → chipstream-0.3.1}/README.rst +0 -0
  28. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStream.icns +0 -0
  29. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStream.ico +0 -0
  30. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStreamLauncher.py +0 -0
  31. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/ChipStreamLauncherCLI.py +0 -0
  32. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/Readme.md +0 -0
  33. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/hook-chipstream.py +0 -0
  34. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/macos_ChipStream.spec +0 -0
  35. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/macos_build_app.sh +0 -0
  36. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/macos_build_requirements.txt +0 -0
  37. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_ChipStream.spec +0 -0
  38. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_build_requirements.txt +0 -0
  39. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_chipstream.iss_dummy +0 -0
  40. {chipstream-0.2.2 → chipstream-0.3.1}/build-recipes/win_make_iss.py +0 -0
  41. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/__init__.py +0 -0
  42. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/__init__.py +0 -0
  43. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/cli/cli_common.py +0 -0
  44. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/__init__.py +0 -0
  45. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/img/chipstream_icon.png +0 -0
  46. {chipstream-0.2.2/chipstream → chipstream-0.3.1/chipstream/gui}/manager.py +0 -0
  47. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/gui/splash.py +0 -0
  48. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream/path_cache.py +0 -0
  49. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/dependency_links.txt +0 -0
  50. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/entry_points.txt +0 -0
  51. {chipstream-0.2.2 → chipstream-0.3.1}/chipstream.egg-info/top_level.txt +0 -0
  52. {chipstream-0.2.2 → chipstream-0.3.1}/docs/artwork/chipstream_icon.svg +0 -0
  53. {chipstream-0.2.2 → chipstream-0.3.1}/docs/artwork/chipstream_splash.png +0 -0
  54. {chipstream-0.2.2 → chipstream-0.3.1}/docs/artwork/chipstream_splash.svg +0 -0
  55. {chipstream-0.2.2 → chipstream-0.3.1}/docs/conf.py +0 -0
  56. {chipstream-0.2.2 → chipstream-0.3.1}/docs/index.rst +0 -0
  57. {chipstream-0.2.2 → chipstream-0.3.1}/docs/requirements.txt +0 -0
  58. {chipstream-0.2.2 → chipstream-0.3.1}/setup.cfg +0 -0
  59. {chipstream-0.2.2 → chipstream-0.3.1}/tests/conftest.py +0 -0
  60. {chipstream-0.2.2 → chipstream-0.3.1}/tests/data/fmt-hdf5_cytoshot_full-features_legacy_allev_2023.zip +0 -0
  61. {chipstream-0.2.2 → chipstream-0.3.1}/tests/helper_methods.py +0 -0
  62. {chipstream-0.2.2 → chipstream-0.3.1}/tests/requirements-full.txt +0 -0
  63. {chipstream-0.2.2 → chipstream-0.3.1}/tests/requirements.txt +0 -0
  64. {chipstream-0.2.2 → chipstream-0.3.1}/tests/test_path_cache.py +0 -0
@@ -19,6 +19,7 @@ jobs:
19
19
  os: [macos-latest, windows-latest]
20
20
  steps:
21
21
  - name: Set env
22
+ shell: bash
22
23
  run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
23
24
  - uses: actions/checkout@v3
24
25
  - name: Set up Python ${{ matrix.python-version }}
@@ -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.2.2
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.17.2
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
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.2.2'
16
- __version_tuple__ = version_tuple = (0, 2, 2)
15
+ __version__ = version = '0.3.1'
16
+ __version_tuple__ = version_tuple = (0, 3, 1)
@@ -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, validate_pixel_size,
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
- bg_cls = cm.bg_methods["sparsemed"]
46
- bg_kwargs = {} # use defaults
47
- bg_id = bg_cls.get_ppid_from_ppkw(bg_kwargs) # use defaults
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.tableView_input.row_selected.connect(self.on_select_job)
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.tableView_input.on_selection_changed)
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.manager.is_busy():
121
+ if not self.job_manager.is_busy():
119
122
  for pp in path_list:
120
- self.tableView_input.add_input_path(pp)
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": inspect.getfullargspec(
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.manager.is_alive()
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.manager.clear()
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.manager.set_output_path(None)
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.manager.set_output_path(path)
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.manager.set_output_path(None)
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.manager.set_output_path(self.path_cache[data])
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.manager.run_all_in_thread(
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.manager.get_info(row)
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>1408</width>
10
- <height>754</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="tableView_input">
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::InternalMove</enum>
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>50</number>
79
+ <number>100</number>
71
80
  </attribute>
72
- <attribute name="horizontalHeaderStretchLastSection">
73
- <bool>true</bool>
81
+ <attribute name="horizontalHeaderDefaultSectionSize">
82
+ <number>100</number>
74
83
  </attribute>
75
- <attribute name="verticalHeaderVisible">
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>1408</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>QTableView</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.2.2
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.17.2
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/test_manager.py
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
@@ -1,4 +1,4 @@
1
- dcnum>=0.17.2
1
+ dcnum>=0.19.1
2
2
  h5py<4,>=3.0.0
3
3
  numpy<2,>=1.21
4
4
 
@@ -27,7 +27,7 @@ classifiers = [
27
27
  ]
28
28
  license = {text = "GPL version 3.0 or later"}
29
29
  dependencies = [
30
- "dcnum>=0.17.2",
30
+ "dcnum>=0.19.1",
31
31
  "h5py>=3.0.0, <4",
32
32
  "numpy>=1.21, <2", # CVE-2021-33430
33
33
  ]
@@ -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 == ("7|"
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
 
@@ -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