Skip to content

Commit

Permalink
Merge branch 'master' into qy/unify-prediction
Browse files Browse the repository at this point in the history
  • Loading branch information
qin-yu committed Dec 17, 2024
2 parents f300890 + f7e8e4b commit f45d5c0
Show file tree
Hide file tree
Showing 20 changed files with 437 additions and 29 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ default_install_hook_types:

repos:
- repo: https://github.com/crate-ci/typos
rev: v1.27.0
rev: typos-dict-v0.11.37
hooks:
- id: typos

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.2
rev: v0.8.1
hooks:
- id: ruff
types_or: [python, pyi, jupyter]
Expand Down
6 changes: 6 additions & 0 deletions docs/chapters/plantseg_interactive_napari/preprocessing.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ This section describes the data processing functionalities available in PlantSeg
## Widget: Image Rescaling

--8<-- "napari/dataprocessing/rescale.md"

## Widget: Image Pair Operations

```python exec="1" html="1"
--8<-- "napari/dataprocessing/image_pair_operations.py"
```
1 change: 1 addition & 0 deletions docs/chapters/python_api/functionals/data_processing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Basic data processing functions are provided in the `dataprocessing` module. The
::: plantseg.functionals.dataprocessing.dataprocessing.image_median
::: plantseg.functionals.dataprocessing.dataprocessing.image_gaussian_smoothing
::: plantseg.functionals.dataprocessing.dataprocessing.image_crop
::: plantseg.functionals.dataprocessing.dataprocessing.process_images

## Segmentation Functions

Expand Down
30 changes: 17 additions & 13 deletions docs/chapters/python_api/tasks/dataprocessing_tasks.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
# Import and export tasks
# Data Processing Tasks

## Gaussian smoothing task
## Image Preprocessing Tasks

### Gaussian smoothing task

::: plantseg.tasks.dataprocessing_tasks.gaussian_smoothing_task

## Image cropping task
### Image cropping task

::: plantseg.tasks.dataprocessing_tasks.image_cropping_task


## Image rescale to shape task
### Image rescale to shape task

::: plantseg.tasks.dataprocessing_tasks.image_rescale_to_shape_task


## Image rescale to voxel size task
### Image rescale to voxel size task

::: plantseg.tasks.dataprocessing_tasks.image_rescale_to_voxel_size_task

## Set image voxel size task
### Set image voxel size task

::: plantseg.tasks.dataprocessing_tasks.set_voxel_size_task

## Label Postprocessing
## Image pair operation task

::: plantseg.tasks.dataprocessing_tasks.image_pair_operation_task

## Label Postprocessing Tasks

## Remove false positives task
### Remove false positives task

::: plantseg.tasks.dataprocessing_tasks.remove_false_positives_by_foreground_probability_task

## Fix Over/Under segmentation task
### Fix Over/Under segmentation task

::: plantseg.tasks.dataprocessing_tasks.fix_over_under_segmentation_from_nuclei_task

## Set biggest object as background task
### Set biggest object as background task

::: plantseg.tasks.dataprocessing_tasks.set_biggest_instance_to_zero_task

## Relabel task
### Relabel task

::: plantseg.tasks.dataprocessing_tasks.relabel_segmentation_task
10 changes: 10 additions & 0 deletions docs/snippets/napari/dataprocessing/image_pair_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys

sys.path.append("docs/snippets")

from napari_widgets_render import render_widget

from plantseg.viewer_napari.widgets import widget_image_pair_operations

html = render_widget(widget_image_pair_operations)
print(html)
2 changes: 1 addition & 1 deletion plantseg/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.0.0a7'
__version__ = '2.0.0a8'
2 changes: 1 addition & 1 deletion plantseg/core/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def to_h5(self, path: Path | str, key: str | None, mode: Literal["a", "w", "w-"]
if isinstance(path, str):
path = Path(path)

if path.suffix not in H5_EXTENSIONS:
if path.suffix.lower() not in H5_EXTENSIONS:
raise ValueError(f"File format {path.suffix} not supported, should be one of {H5_EXTENSIONS}")

key = key if key is not None else self.name
Expand Down
15 changes: 15 additions & 0 deletions plantseg/functionals/dataprocessing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
remove_false_positives_by_foreground_probability,
)
from plantseg.functionals.dataprocessing.dataprocessing import (
ImagePairOperation,
add_images,
compute_scaling_factor,
compute_scaling_voxelsize,
divide_images,
fix_layout,
fix_layout_to_CYX,
fix_layout_to_CZYX,
Expand All @@ -14,10 +17,14 @@
image_gaussian_smoothing,
image_median,
image_rescale,
max_images,
multiply_images,
normalize_01,
normalize_01_channel_wise,
process_images,
scale_image_to_voxelsize,
select_channel,
subtract_images,
)
from plantseg.functionals.dataprocessing.labelprocessing import (
relabel_segmentation,
Expand Down Expand Up @@ -45,6 +52,14 @@
"fix_layout_to_ZYX",
"fix_layout_to_YX",
"fix_layout",
# simple image operations
"ImagePairOperation",
"process_images",
"max_images",
"add_images",
"subtract_images",
"multiply_images",
"divide_images",
# labelprocessing
"relabel_segmentation",
"set_background_to_value",
Expand Down
152 changes: 152 additions & 0 deletions plantseg/functionals/dataprocessing/dataprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,155 @@ def normalize_01_channel_wise(data: np.ndarray, channel_axis: int = 0, eps=1e-12

# Move the axis back to its original position
return np.moveaxis(normalized_channels, 0, channel_axis)


ImagePairOperation = Literal["add", "multiply", "subtract", "divide", "max"]


def process_images(
image1: np.ndarray,
image2: np.ndarray,
operation: ImagePairOperation,
normalize_input: bool = False,
clip_output: bool = False,
normalize_output: bool = True,
) -> np.ndarray:
"""
General function for performing image operations with optional preprocessing and post-processing.
Args:
image1 (np.ndarray): First input image.
image2 (np.ndarray): Second input image.
operation (str): Operation to perform ('add', 'multiply', 'subtract', 'divide', 'max').
normalize_input (bool): Whether to normalize the input images to the range [0, 1]. Default is False.
clip_output (bool): Whether to clip the resulting image values to the range [0, 1]. Default is False.
normalize_output (bool): Whether to normalize the output image to the range [0, 1]. Default is True.
Returns:
np.ndarray: The resulting image after performing the operation.
"""
# Preprocessing: Normalize input images if specified
if normalize_input:
image1, image2 = normalize_01(image1), normalize_01(image2)

# Perform the specified operation
if operation == "add":
result = image1 + image2
elif operation == "multiply":
result = image1 * image2
elif operation == "subtract":
result = image1 - image2
elif operation == "divide":
result = image1 / image2
elif operation == "max":
result = np.maximum(image1, image2)
else:
raise ValueError(f"Unsupported operation: {operation}")

# Post-processing: Clip and/or normalize output if specified
if clip_output:
result = np.clip(result, 0, 1)
if normalize_output:
result = normalize_01(result)

return result


def add_images(
image1: np.ndarray,
image2: np.ndarray,
clip_output: bool = False,
normalize_output: bool = True,
normalize_input: bool = False,
) -> np.ndarray:
"""
Adds two images with optional preprocessing and post-processing.
"""
return process_images(
image1,
image2,
operation="add",
clip_output=clip_output,
normalize_output=normalize_output,
normalize_input=normalize_input,
)


def multiply_images(
image1: np.ndarray,
image2: np.ndarray,
clip_output: bool = False,
normalize_output: bool = True,
normalize_input: bool = False,
) -> np.ndarray:
"""
Multiplies two images with optional preprocessing and post-processing.
"""
return process_images(
image1,
image2,
operation="multiply",
clip_output=clip_output,
normalize_output=normalize_output,
normalize_input=normalize_input,
)


def subtract_images(
image1: np.ndarray,
image2: np.ndarray,
clip_output: bool = False,
normalize_output: bool = True,
normalize_input: bool = False,
) -> np.ndarray:
"""
Subtracts the second image from the first with optional preprocessing and post-processing.
"""
return process_images(
image1,
image2,
operation="subtract",
clip_output=clip_output,
normalize_output=normalize_output,
normalize_input=normalize_input,
)


def divide_images(
image1: np.ndarray,
image2: np.ndarray,
clip_output: bool = False,
normalize_output: bool = True,
normalize_input: bool = False,
) -> np.ndarray:
"""
Divides the first image by the second with optional preprocessing and post-processing.
"""
return process_images(
image1,
image2,
operation="divide",
clip_output=clip_output,
normalize_output=normalize_output,
normalize_input=normalize_input,
)


def max_images(
image1: np.ndarray,
image2: np.ndarray,
clip_output: bool = False,
normalize_output: bool = True,
normalize_input: bool = False,
) -> np.ndarray:
"""
Computes the pixel-wise maximum of two images with optional preprocessing and post-processing.
"""
return process_images(
image1,
image2,
operation="max",
clip_output=clip_output,
normalize_output=normalize_output,
normalize_input=normalize_input,
)
2 changes: 1 addition & 1 deletion plantseg/headless/headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def parse_import_image_task(input_path, allow_dir: bool) -> list[Path]:
else:
raise ValueError(f"Path {input_path} is not a file or a directory.")

list_files = [f for f in list_files if f.suffix in allowed_data_format]
list_files = [f for f in list_files if f.suffix.lower() in allowed_data_format]
if not list_files:
raise ValueError(f"No valid files found in {input_path}.")

Expand Down
4 changes: 2 additions & 2 deletions plantseg/io/h5.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@


def _validate_h5_file(path: Path) -> None:
assert path.suffix in H5_EXTENSIONS, f"File extension not supported. Supported extensions: {H5_EXTENSIONS}"
assert path.suffix.lower() in H5_EXTENSIONS, f"File extension not supported. Supported extensions: {H5_EXTENSIONS}"
assert path.exists(), f"File not found: {path}"


Expand Down Expand Up @@ -73,7 +73,7 @@ def load_h5(
np.ndarray: dataset as numpy array
"""

assert path.suffix in H5_EXTENSIONS, f"File extension not supported. Supported extensions: {H5_EXTENSIONS}"
assert path.suffix.lower() in H5_EXTENSIONS, f"File extension not supported. Supported extensions: {H5_EXTENSIONS}"
assert path.exists(), f"File not found: {path}"

with h5py.File(path, "r") as f:
Expand Down
10 changes: 5 additions & 5 deletions plantseg/io/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def smart_load(path: Path, key: str | None = None, default=load_tiff) -> np.ndar
>>> data = smart_load('path/to/file.h5', key='raw')
"""
ext = path.suffix
ext = (path.suffix).lower()
if key == "":
key = None

Expand Down Expand Up @@ -72,7 +72,7 @@ def smart_load_with_vs(path: Path, key: str | None = None, default=load_tiff) ->
>>> data = smart_load('path/to/file.h5', key='raw')
"""
ext = path.suffix
ext = (path.suffix).lower()
if key == "":
key = None

Expand All @@ -85,9 +85,9 @@ def smart_load_with_vs(path: Path, key: str | None = None, default=load_tiff) ->
if ext in PIL_EXTENSIONS:
return load_pil(path), None

if ".zarr" in path.suffixes:
if ext in ZARR_EXTENSIONS:
return load_zarr(path, key), read_zarr_voxel_size(path, key)

else:
logger.warning(f"No default found for {ext}, reverting to default loader.")
return default(path)
logger.warning(f"No default found for {ext}, reverting to default loader with no voxel size reader.")
return default(path), None
Loading

0 comments on commit f45d5c0

Please sign in to comment.