Seg for lost IDs 2#1098
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends “Segmentation for lost IDs” to support selecting/running multiple segmentation models via a persisted “recipe”, adds a per-GUI cache for imported segmentation modules, and improves logging robustness for Unicode output.
Changes:
- Add multi-model selection UI (including “select last selection/recipe”) and persist/load a JSON recipe for “Seg for lost IDs”.
- Update the SegForLostIDs worker to iterate through multiple model configurations and pass explicit model/post-processing settings into
single_cell_seg. - Harden stdout/file logging against Unicode encoding issues and set UTF-8 encoding for log files.
Reviewed changes
Copilot reviewed 5 out of 17 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| cellacdc/workers.py | Refactors SegForLostIDs worker to run multiple models and reuse imported modules; updates regionprops usage. |
| cellacdc/segm_utils.py | Updates single_cell_seg signature to accept explicit model and post-processing kwargs (instead of a GUI “win”). |
| cellacdc/myutils.py | Makes logger output more resilient to Unicode and writes log files as UTF-8. |
| cellacdc/gui.py | Adds multi-model selection + recipe persistence/loading + module cache initialization for SegForLostIDs. |
| cellacdc/apps.py | Adds new multi-pick model selection widgets and extends QDialogSelectModel for multi-selection workflows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…Cell_ACDC into seg_for_lost_IDs_2
ElpadoCan
left a comment
There was a problem hiding this comment.
Things found upon review:
- Circular import of
load.loadDataincore.py - Move
apps.MultiPickListWidgettowidgets.MultiPickListWidget - Use the same stylesheet found in
cellacdc._palettes.ListWidgetStyleSheetfor the newMultiPickListWidget
| original_bbox_lab_cleared_borders = skimage.segmentation.clear_border(original_bbox_lab) | ||
| box_model_lab = model_lab[box_x_min:box_x_max, box_y_min:box_y_max] | ||
|
|
||
| # original_bbox_lab[np.isin(original_bbox_lab, IDs)] = 0 should be a given. If not seg for lost IDs this recommended |
| if ( | ||
| len(selected_models) == 1 | ||
| and selected_models[0] == 'Automatic thresholding' | ||
| ): | ||
| self.selectedModel = 'thresholding' | ||
| else: | ||
| self.selectedModel = selected_models |
| monkeypatch.setattr(worker, 'emitSigAskInit', lambda: None) | ||
| monkeypatch.setattr(worker, 'emitSigAskInstallGPU', lambda base_model_name, use_gpu: None) | ||
| monkeypatch.setattr(worker, 'emitSigUpdateRP', lambda wl_update=True, wl_track_og_curr=False: None) | ||
| monkeypatch.setattr(worker, 'emitSigStoreData', lambda autosave=True: None) | ||
| monkeypatch.setattr(worker, 'emitTrackManuallyAddedObject', lambda *args, **kwargs: None) | ||
| monkeypatch.setattr(myutils, 'import_segment_module', lambda base_model_name: SimpleNamespace(Model=ThresholdingModel)) | ||
| monkeypatch.setattr(myutils, 'init_segm_model', lambda acdcSegment, posData, init_kwargs_new: ThresholdingModel()) | ||
|
|
||
| worker.run() |
| if 'device' in init_kwargs: | ||
| init_kwargs_new = dict(init_kwargs_new, device='cpu') | ||
| if 'use_gpu' in init_kwargs: | ||
| init_kwargs_new = dict(init_kwargs_new, use_gpu=False) |
| target_ID = segm_utils.get_best_overlapping_label( | ||
| prev_bbox_lab, | ||
| obj, | ||
| missing_IDs_global, | ||
| ) | ||
| if target_ID is None: | ||
| continue | ||
|
|
||
| filtered_IDs.append(obj.label) | ||
| relabeled_IDs[obj.label] = target_ID |
| self._stdout.write(text.encode( | ||
| self._stdout.encoding, errors='replace' | ||
| ).decode(self._stdout.encoding)) |
| def _addCounterWidget(self, label, item): | ||
| rowWidget = QWidget() | ||
| rowLayout = QHBoxLayout(rowWidget) | ||
| rowLayout.setContentsMargins(4, 0, 4, 0) | ||
| rowLayout.setSpacing(6) | ||
|
|
||
| nameLabelPlaceholder = QSpacerItem(2, 0) | ||
| minusBtn = QPushButton('-') | ||
| plusBtn = QPushButton('+') | ||
| countLabel = QLabel(str(self._countMap.get(label, 0))) | ||
|
|
||
| minusBtn.setFixedWidth(24) | ||
| plusBtn.setFixedWidth(24) | ||
| countLabel.setMinimumWidth(20) | ||
| countLabel.setAlignment(Qt.AlignCenter) | ||
|
|
||
| minusBtn.clicked.connect(lambda _, lbl=label: self.removeSelection(lbl)) | ||
| plusBtn.clicked.connect(lambda _, lbl=label: self.addSelection(lbl)) | ||
|
|
||
| rowLayout.addItem(nameLabelPlaceholder) | ||
| rowLayout.addStretch(1) | ||
| rowLayout.addWidget(minusBtn) | ||
| rowLayout.addWidget(countLabel) | ||
| rowLayout.addWidget(plusBtn) |
| 'Use this dialog to define the segmentation workflow used for ' | ||
| 'resegmenting local neighborhood lost IDs. Other already segmented cells are filled ' | ||
| 'with background, which makes even dimm cells seem bright after ' | ||
| 'rescaling before resegmentation. This is especially usefull for ' | ||
| 'cells which have varying intensities over time, like FUCCI cells. <br><br>' |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 24 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
cellacdc/segm_utils.py:326
single_cell_segcurrently assigns a freshnew_unique_IDto every segmented object in the bbox. This prevents the SegForLostIDs flow from recovering the original (missing) IDs, even thoughget_best_overlapping_labelwas added for this purpose.
Instead, for each segmented object, map it back to the best-overlapping label in the previous-frame bbox among the requested IDs; only fall back to new_unique_ID when no allowed overlap exists.
rp_model_lab = regionprops.acdcRegionprops(box_model_lab,precache_centroids=False)
for obj in rp_model_lab:
box_curr_lab_other_IDs[box_model_lab == obj.label] = new_unique_ID
assigned_IDs.append(new_unique_ID)
new_unique_ID += 1
| def emitSigUpdateRP(self, wl_track_og_curr, wl_update): | ||
| self.mutex.lock() | ||
| self.sigUpdateRP.emit(wl_track_og_curr, wl_update) | ||
| self.waitCond.wait(self.mutex) | ||
| self.mutex.unlock() |
| monkeypatch.setattr(myutils, 'import_segment_module', lambda base_model_name: SimpleNamespace(Model=ThresholdingModel)) | ||
| monkeypatch.setattr(myutils, 'init_segm_model', lambda acdcSegment, posData, init_kwargs_new: ThresholdingModel()) | ||
|
|
||
| worker.run() |
No description provided.