Skip to content

Commit c5ec9ee

Browse files
authored
Merge branch 'master' into fixHalo
2 parents b65ae75 + 5d2a22c commit c5ec9ee

File tree

8 files changed

+153
-99
lines changed

8 files changed

+153
-99
lines changed

README.md

+1-70
Original file line numberDiff line numberDiff line change
@@ -301,77 +301,8 @@ the boundary signal, we could leverage the fact that one cell contains only one
301301
segmentation strategy and obtain improved segmentation. This workflow is now available in all PlantSeg interfaces.
302302

303303
## Troubleshooting
304-
* If you stumble in the following error message:
305-
```
306-
AssertionError:
307-
The NVIDIA driver on your system is too old (found version xxxx).
308-
Please update your GPU driver by downloading and installing a new
309-
version from the URL: http://www.nvidia.com/Download/index.aspx
310-
Alternatively, go to: http://pytorch.org to install
311-
a PyTorch version that has been compiled with your version
312-
of the CUDA driver.
313-
```
314-
or:
315-
```
316-
raise AssertionError("Torch not compiled with CUDA enabled")
317-
AssertionError: Torch not compiled with CUDA enabled
318-
```
319-
It means that your cuda installation does not match the default in plantseg.
320-
You can check your current cuda version by typing in the terminal
321-
```
322-
cat /usr/local/cuda/version.txt
323-
```
324-
Then you can re-install the pytorch version compatible with your cuda by activating your `plant-seg` environment:
325-
```
326-
conda activate plant-seg
327-
```
328-
and
329-
```
330-
conda install -c pytorch torchvision cudatoolkit=<YOU_CUDA_VERSION> pytorch
331-
```
332-
e.g. for cuda 9.2
333-
```
334-
conda install -c pytorch torchvision cudatoolkit=9.2 pytorch
335-
```
336-
337-
Alternatively one can create the `plant-seg` environment from scratch and ensuring the correct version of cuda/pytorch, by:
338-
```
339-
conda create -n plant-seg -c lcerrone -c abailoni -c cpape -c conda-forge cudatoolkit=<YOU_CUDA_VERSION> plantseg
340-
```
341-
342-
* If you use plantseg from the GUI and you receive an error similar to:
343-
```
344-
RuntimeError: key : 'crop_volume' is missing, plant-seg requires 'crop_volume' to run
345-
```
346-
(or a similar message for any of the other keys)
347-
It might be that the last session configuration file got corrupted or is outdated.
348-
You should be able to solve it by removing the corrupted file `config_gui_last.yaml`.
349-
350-
If you have a standard installation of plantseg, you can remove it by executing on the terminal:
351-
```
352-
$ rm ~/.plantseg_models/configs/config_gui_last.yaml
353-
```
354-
355-
* If you use plantseg from the command line, and you receive an error similar to:
356-
```
357-
RuntimeError: key : 'crop_volume' is missing, plant-seg requires 'crop_volume' to run
358-
```
359-
360-
Please make sure that your configuration has the correct formatting and contains all required keys.
361-
An updated example can be found inside the directory `examples`, in this repository.
362-
363-
* If when trying to execute the Lifted Multicut pipeline you receive an error like:
364-
```
365-
'cannot import name 'lifted_problem_from_probabilities' from 'elf.segmentation.features''
366-
```
367-
The solution is to re-install [elf](https://github.com/constantinpape/elf) via
368-
```
369-
conda install -c conda-forge python-elf
370-
```
371304

372-
* PlantSeg is under active development, so it may happen that the models/configuration files saved in `~/.plantseg_modes`
373-
are outdated. In case of errors related to loading the configuration file, please close the PlantSeg app,
374-
remove `~/.plantseg_models` directory and try again.
305+
See [troubleshooting](https://hci-unihd.github.io/plant-seg/chapters/getting_started/troubleshooting) for a list of common issues and their solutions.
375306

376307
## Tests
377308
In order to run tests make sure that `pytest` is installed in your conda environment. You can run your tests

docs/chapters/getting_started/troubleshooting.md

+45-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
1-
# Troubleshooting
1+
# Troubleshooting <!-- omit in toc -->
22

3-
* If you stumble in the following error message:
3+
- [`Could not load library libcudnn_ops_infer.so.8.`](#could-not-load-library-libcudnn_ops_inferso8)
4+
- [`NVIDIA driver on your system is too old` or `Torch not compiled with CUDA enabled`](#nvidia-driver-on-your-system-is-too-old-or-torch-not-compiled-with-cuda-enabled)
5+
- [`RuntimeError: key : KEY_NAME is missing, plant-seg requires KEY_NAME to run`](#runtimeerror-key--key_name-is-missing-plant-seg-requires-key_name-to-run)
6+
- [`cannot import name 'lifted_problem_from_probabilities'`](#cannot-import-name-lifted_problem_from_probabilities)
7+
- [Other issues](#other-issues)
8+
9+
----
10+
11+
#### `Could not load library libcudnn_ops_infer.so.8.`
12+
13+
If you stumble in the following error message:
14+
```
15+
Could not load library libcudnn_ops_infer.so.8. Error: libcudnn_ops_infer.so.8: cannot open shared object file: No such file or directory
16+
```
17+
18+
Just install `cudnn` by running:
19+
```
20+
$ mamba install -c conda-forge cudnn
21+
```
22+
23+
----
24+
25+
#### `NVIDIA driver on your system is too old` or `Torch not compiled with CUDA enabled`
26+
27+
If you stumble in the following error message:
428
```
529
AssertionError:
630
The NVIDIA driver on your system is too old (found version xxxx).
@@ -35,10 +59,14 @@ conda install -c pytorch torchvision cudatoolkit=9.2 pytorch
3559

3660
Alternatively one can create the `plant-seg` environment from scratch and ensuring the correct version of cuda/pytorch, by:
3761
```
38-
conda create -n plant-seg -c lcerrone -c abailoni -c cpape -c awolny -c conda-forge cudatoolkit=<YOU_CUDA_VERSION> plantseg
62+
conda create -n plant-seg -c lcerrone -c abailoni -c cpape -c conda-forge cudatoolkit=<YOU_CUDA_VERSION> plantseg
3963
```
4064

41-
* If you use plantseg from the GUI and you receive an error similar to:
65+
----
66+
67+
#### `RuntimeError: key : KEY_NAME is missing, plant-seg requires KEY_NAME to run`
68+
69+
If you use plantseg from the GUI and you receive an error similar to:
4270
```
4371
RuntimeError: key : 'crop_volume' is missing, plant-seg requires 'crop_volume' to run
4472
```
@@ -51,15 +79,19 @@ If you have a standard installation of plantseg, you can remove it by executing
5179
$ rm ~/.plantseg_models/configs/config_gui_last.yaml
5280
```
5381

54-
* If you use plantseg from the comand line and you receive an error similar to:
82+
If you use plantseg from the command line, and you receive an error similar to:
5583
```
5684
RuntimeError: key : 'crop_volume' is missing, plant-seg requires 'crop_volume' to run
5785
```
5886

59-
Please make sure that your configuratiuon has the correct formatting and contains all required keys.
87+
Please make sure that your configuration has the correct formatting and contains all required keys.
6088
An updated example can be found inside the directory `examples`, in this repository.
6189

62-
* If when trying to execute the Lifted Multicut pipeline you receive an error like:
90+
----
91+
92+
#### `cannot import name 'lifted_problem_from_probabilities'`
93+
94+
If when trying to execute the Lifted Multicut pipeline you receive an error like:
6395
```
6496
'cannot import name 'lifted_problem_from_probabilities' from 'elf.segmentation.features''
6597
```
@@ -68,6 +100,10 @@ The solution is to re-install [elf](https://github.com/constantinpape/elf) via
68100
conda install -c conda-forge python-elf
69101
```
70102

71-
* PlantSeg is under active development so it may happen that the models/configuration files saved in `~/.plantseg_modes`
103+
----
104+
105+
#### Other issues
106+
107+
* PlantSeg is under active development, so it may happen that the models/configuration files saved in `~/.plantseg_modes`
72108
are outdated. In case of errors related to loading the configuration file, please close the PlantSeg app,
73-
remove `~/.plantseg_models` directory and try again.
109+
remove `~/.plantseg_models` directory and try again.

plantseg/dataprocessing/functional/advanced_dataprocessing.py

+51-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import numpy as np
66
import tqdm
77
from skimage.filters import gaussian
8-
from skimage.segmentation import watershed
9-
8+
from skimage.segmentation import watershed, relabel_sequential
9+
from skimage.measure import regionprops
1010

1111
def get_bbox(mask: np.array, pixel_toll: int = 0) -> tuple[tuple, int, int, int]:
1212
"""
@@ -235,3 +235,52 @@ def fix_over_under_segmentation_from_nuclei(cell_seg: np.array,
235235
cell_assignments,
236236
cell_idx=None)
237237
return _cell_seg
238+
239+
240+
def remove_false_positives_by_foreground_probability(segmentation: np.array,
241+
foreground: np.array,
242+
threshold: float) -> np.array:
243+
"""
244+
Remove false positive regions in a segmentation based on a foreground probability map in a smart way.
245+
If the mean(an instance * its own probability region) < threshold, it is removed.
246+
247+
Args:
248+
segmentation (np.ndarray): The segmentation array, where each unique non-zero value indicates a distinct region.
249+
foreground (np.ndarray): The foreground probability map, same shape as `segmentation`.
250+
threshold (float): Probability threshold below which regions are considered false positives.
251+
252+
Returns:
253+
np.ndarray: The modified segmentation array with false positives removed.
254+
"""
255+
# TODO: make a channel for removed regions for easier inspection
256+
# TODO: use `relabel_sequential` to recover the original labels
257+
258+
if not segmentation.shape == foreground.shape:
259+
raise ValueError("Shape of segmentation and probability map must match.")
260+
if foreground.max() > 1:
261+
raise ValueError("Foreground must be a probability map probability map.")
262+
263+
instances, _, _ = relabel_sequential(segmentation)
264+
265+
regions = regionprops(instances)
266+
to_keep = np.ones(len(regions) + 1)
267+
pixel_count = np.zeros(len(regions) + 1)
268+
pixel_value = np.zeros(len(regions) + 1)
269+
270+
for region in tqdm.tqdm(regions):
271+
bbox = region.bbox
272+
cube = instances[bbox[0]:bbox[3], bbox[1]:bbox[4], bbox[2]:bbox[5]] == region.label # other instances may exist, don't use `> 0`
273+
prob = foreground[bbox[0]:bbox[3], bbox[1]:bbox[4], bbox[2]:bbox[5]]
274+
pixel_count[region.label] = region.area
275+
pixel_value[region.label] = (cube * prob).sum()
276+
277+
likelihood = pixel_value / pixel_count
278+
to_keep[likelihood < threshold] = 0
279+
ids_to_delete = np.argwhere(to_keep == 0)
280+
assert ids_to_delete.shape[1] == 1
281+
ids_to_delete = ids_to_delete.flatten()
282+
# print(f" Removing instance {region.label}: pixel count: {pixel_count}, pixel value: {pixel_value}, likelihood: {likelihood}")
283+
284+
instances[np.isin(instances, ids_to_delete)] = 0
285+
instances, _, _ = relabel_sequential(instances)
286+
return instances

plantseg/segmentation/gasp.py

-2
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ def __init__(self,
5656

5757
# Postprocessing size threshold
5858
self.post_minsize = post_minsize
59-
6059
self.n_threads = n_threads
61-
6260
self.dt_watershed = partial(dt_watershed,
6361
threshold=ws_threshold, sigma_seeds=ws_sigma,
6462
stacked=ws_2D, sigma_weights=ws_w_sigma,

plantseg/utils.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def load_config(config_path: str) -> dict:
2626
return config
2727

2828

29-
def get_model_zoo() -> dict:
29+
def get_model_zoo(get_custom: bool = True) -> dict:
3030
"""
3131
returns a dictionary of all models in the model zoo.
3232
example:
@@ -43,22 +43,24 @@ def get_model_zoo() -> dict:
4343

4444
zoo_config = load_config(zoo_config)
4545

46-
custom_zoo_config = load_config(custom_zoo)
46+
if get_custom:
47+
custom_zoo_config = load_config(custom_zoo)
4748

48-
if custom_zoo_config is None:
49-
custom_zoo_config = {}
49+
if custom_zoo_config is None:
50+
custom_zoo_config = {}
5051

51-
zoo_config.update(custom_zoo_config)
52+
zoo_config.update(custom_zoo_config)
5253
return zoo_config
5354

5455

5556
def list_models(dimensionality_filter: list[str] = None,
5657
modality_filter: list[str] = None,
57-
output_type_filter: list[str] = None) -> list[str]:
58+
output_type_filter: list[str] = None,
59+
use_custom_models: bool = True) -> list[str]:
5860
"""
5961
return a list of models in the model zoo by name
6062
"""
61-
zoo_config = get_model_zoo()
63+
zoo_config = get_model_zoo(use_custom_models)
6264
models = list(zoo_config.keys())
6365

6466
if dimensionality_filter is not None:

plantseg/viewer/containers.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from plantseg.viewer.widget.proofreading.proofreading import widget_split_and_merge_from_scribbles
1414
from plantseg.viewer.widget.segmentation import widget_dt_ws, widget_agglomeration
1515
from plantseg.viewer.widget.segmentation import widget_fix_over_under_segmentation_from_nuclei
16+
from plantseg.viewer.widget.segmentation import widget_fix_false_positive_from_foreground_pmap
1617
from plantseg.viewer.widget.segmentation import widget_lifted_multicut
1718
from plantseg.viewer.widget.segmentation import widget_simple_dt_ws
1819

@@ -65,7 +66,8 @@ def get_gasp_workflow():
6566
def get_extra_seg():
6667
container = MainWindow(widgets=[widget_dt_ws,
6768
widget_lifted_multicut,
68-
widget_fix_over_under_segmentation_from_nuclei],
69+
widget_fix_over_under_segmentation_from_nuclei,
70+
widget_fix_false_positive_from_foreground_pmap],
6971
labels=False)
7072
container = setup_menu(container, path='https://hci-unihd.github.io/plant-seg/chapters/plantseg_interactive_napari/extra_seg.html')
7173
return container

plantseg/viewer/widget/predictions.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,12 @@ def _on_model_name_changed(model_name: str):
161161
widget_unet_predictions.model_name.tooltip = f'Select a pretrained model. Current model description: {description}'
162162

163163

164-
def _compute_multiple_predictions(image, patch_size, patch_halo, device):
164+
def _compute_multiple_predictions(image, patch_size, patch_halo, device, use_custom_models=True):
165165
out_layers = []
166-
for i, model_name in enumerate(list_models()):
166+
model_list = list_models(use_custom_models=use_custom_models)
167+
for i, model_name in enumerate(model_list):
167168

168-
napari_formatted_logging(f'Running UNet Predictions: {model_name} {i}/{len(list_models())}',
169+
napari_formatted_logging(f'Running UNet Predictions: {model_name} {i}/{len(model_list)}',
169170
thread='UNet Grid Predictions')
170171

171172
out_name = create_layer_name(image.name, model_name)
@@ -193,17 +194,21 @@ def _compute_multiple_predictions(image, patch_size, patch_halo, device):
193194
patch_halo={'label': 'Patch halo',
194195
'tooltip': 'Patch halo is extra padding for correct prediction on image boarder.'},
195196
device={'label': 'Device',
196-
'choices': ALL_DEVICES}
197+
'choices': ALL_DEVICES},
198+
use_custom_models={'label': 'Use custom models',
199+
'tooltip': 'If True, custom models will also be used.'}
197200
)
198201
def widget_test_all_unet_predictions(image: Image,
199202
patch_size: Tuple[int, int, int] = (80, 170, 170),
200203
patch_halo: Tuple[int, int, int] = (2, 4, 4),
201-
device: str = ALL_DEVICES[0]) -> Future[List[LayerDataTuple]]:
204+
device: str = ALL_DEVICES[0],
205+
use_custom_models: bool = True) -> Future[List[LayerDataTuple]]:
202206
func = thread_worker(partial(_compute_multiple_predictions,
203207
image=image,
204208
patch_size=patch_size,
205209
patch_halo=patch_halo,
206-
device=device))
210+
device=device,
211+
use_custom_models=use_custom_models,))
207212

208213
future = Future()
209214

plantseg/viewer/widget/segmentation.py

+33-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from napari.types import LayerDataTuple
88

99
from napari import Viewer
10-
from plantseg.dataprocessing.functional.advanced_dataprocessing import fix_over_under_segmentation_from_nuclei
10+
from plantseg.dataprocessing.functional.advanced_dataprocessing import fix_over_under_segmentation_from_nuclei, remove_false_positives_by_foreground_probability
1111
from plantseg.dataprocessing.functional.dataprocessing import normalize_01
1212
from plantseg.segmentation.functional import gasp, multicut, dt_watershed, mutex_ws
1313
from plantseg.segmentation.functional import lifted_multicut_from_nuclei_segmentation, lifted_multicut_from_nuclei_pmaps
@@ -330,5 +330,36 @@ def widget_fix_over_under_segmentation_from_nuclei(cell_segmentation: Labels,
330330
input_keys=inputs_names,
331331
layer_kwarg=layer_kwargs,
332332
layer_type=layer_type,
333-
step_name=f'Fix Over / Under segmentation',
333+
step_name='Fix Over / Under Segmentation',
334+
)
335+
336+
337+
@magicgui(call_button='Run Segmentation Fix from Foreground Pmap',
338+
segmentation={'label': 'Segmentation'},
339+
foreground={'label': 'Foreground Pmap'},
340+
threshold={'label': 'Threshold',
341+
'widget_type': 'FloatSlider', 'max': 1., 'min': 0.})
342+
def widget_fix_false_positive_from_foreground_pmap(segmentation: Labels,
343+
foreground: Image, # TODO: maybe also allow labels
344+
threshold=0.6) -> Future[LayerDataTuple]:
345+
out_name = create_layer_name(segmentation.name, 'FGPmapFix')
346+
347+
inputs_names = (segmentation.name, foreground.name)
348+
func_kwargs = {'segmentation': segmentation.data,
349+
'foreground': foreground.data}
350+
351+
layer_kwargs = layer_properties(name=out_name,
352+
scale=segmentation.scale,
353+
metadata=segmentation.metadata)
354+
layer_type = 'labels'
355+
step_kwargs = dict(threshold=threshold)
356+
357+
return start_threading_process(remove_false_positives_by_foreground_probability,
358+
runtime_kwargs=func_kwargs,
359+
statics_kwargs=step_kwargs,
360+
out_name=out_name,
361+
input_keys=inputs_names,
362+
layer_kwarg=layer_kwargs,
363+
layer_type=layer_type,
364+
step_name='Reduce False Positives',
334365
)

0 commit comments

Comments
 (0)