Skip to content

Commit

Permalink
Merge PR #370 | Add image pair operation widget/task/functionals/docs
Browse files Browse the repository at this point in the history
  • Loading branch information
qin-yu authored Dec 3, 2024
2 parents 5ffaf72 + a782a70 commit 29e9fca
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-publish-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
push:
branches:
- master
- qy/add-plantseg-v1-installation
- qy/mask-pred

permissions:
contents: write
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)
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,
)
37 changes: 37 additions & 0 deletions plantseg/tasks/dataprocessing_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

from plantseg.core.image import ImageDimensionality, ImageLayout, PlantSegImage, SemanticType
from plantseg.functionals.dataprocessing import (
ImagePairOperation,
fix_over_under_segmentation_from_nuclei,
image_gaussian_smoothing,
image_rescale,
process_images,
relabel_segmentation,
remove_false_positives_by_foreground_probability,
set_biggest_instance_to_zero,
Expand Down Expand Up @@ -306,3 +308,38 @@ def relabel_segmentation_task(image: PlantSegImage, background: int | None = Non
new_data = relabel_segmentation(data, background=background)
new_image = image.derive_new(new_data, name=f"{image.name}_relabeled")
return new_image


@task_tracker
def image_pair_operation_task(
image1: PlantSegImage,
image2: PlantSegImage,
operation: ImagePairOperation,
normalize_input: bool = False,
clip_output: bool = False,
normalize_output: bool = False,
) -> PlantSegImage:
"""
Task to perform an operation on two images.
Args:
image1 (PlantSegImage): First image to process.
Image2 (PlantSegImage): Second image to process.
operation (str): Operation to perform on the images.
normalize_input (bool): Normalize input images before processing.
clip_output (bool): Clip output values to the range [0, 1].
normalize_output (bool): Normalize output values to the range [0, 1].
Returns:
PlantSegImage: New image resulting from the operation.
"""
result = process_images(
image1.get_data(),
image2.get_data(),
operation=operation,
normalize_input=normalize_input,
clip_output=clip_output,
normalize_output=normalize_output,
)
new_image = image1.derive_new(result, name=f"{image1.name}_{operation}_{image2.name}")
return new_image
2 changes: 2 additions & 0 deletions plantseg/viewer_napari/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
widget_filter_segmentation,
widget_fix_over_under_segmentation_from_nuclei,
widget_gaussian_smoothing,
widget_image_pair_operations,
widget_infos,
widget_open_file,
widget_proofreading_initialisation,
Expand Down Expand Up @@ -55,6 +56,7 @@ def get_preprocessing_tab():
widget_gaussian_smoothing,
widget_rescaling,
widget_cropping,
widget_image_pair_operations,
],
labels=False,
)
Expand Down
2 changes: 2 additions & 0 deletions plantseg/viewer_napari/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
widget_cropping,
widget_fix_over_under_segmentation_from_nuclei,
widget_gaussian_smoothing,
widget_image_pair_operations,
widget_relabel,
widget_remove_false_positives_by_foreground,
widget_rescaling,
Expand Down Expand Up @@ -40,6 +41,7 @@
"widget_gaussian_smoothing",
"widget_rescaling",
"widget_cropping",
"widget_image_pair_operations",
# IO
"widget_open_file",
"widget_export_image",
Expand Down
Loading

0 comments on commit 29e9fca

Please sign in to comment.