Skip to content

Commit

Permalink
Add morphological_operations tool (#106)
Browse files Browse the repository at this point in the history
* Add `morphological_operations` tool

Squashed commit of the following:

commit 0edf531
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 23:48:36 2024 +0100

    Update test data

commit cf17824
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 22:44:12 2024 +0000

    Fix UI

commit 9992a54
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 22:29:58 2024 +0000

    Fix bug

commit 5bb0d2c
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 23:26:35 2024 +0100

    Update test output data

commit d720124
Merge: 1408aab c57e084
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 22:22:47 2024 +0000

    Merge branch 'add-morphology-tools' of https://github.com/kostrykin/galaxy-image-analysis into add-morphology-tools

commit 1408aab
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 22:22:14 2024 +0000

    Fix output type

commit c57e084
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 23:16:53 2024 +0100

    Add test results

commit addf5c1
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 22:16:35 2024 +0000

    Add more tests

commit edf0dd5
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 22:07:40 2024 +0000

    Fix bug

commit 9094e30
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:44:13 2024 +0000

    Fix tool

commit 77bd875
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:40:00 2024 +0000

    Fix tool

commit 0884095
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:37:59 2024 +0000

    Fix profile

commit 3220ddb
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:37:05 2024 +0000

    Add help

commit 657018c
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:32:07 2024 +0000

    Add citation

commit 5df1d74
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:29:01 2024 +0000

    Add tests for `morphological_operations` tool

commit 59aa195
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:15:58 2024 +0000

    Fix `morphological_operations` tool

commit e048176
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 21:07:08 2024 +0000

    Finish implementation of `morphological_operations` tool

commit 106075c
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 18:56:08 2024 +0000

    Continue implementation of `morphological_operations` tool

commit 8057408
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 13:31:47 2024 +0000

    Continue implementation of `morphological_operations` tool

commit 442a459
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 13:04:26 2024 +0000

    Add `morphological_operations` tool

commit 0ab2a2d
Author: Leonid Kostrykin <[email protected]>
Date:   Thu Mar 7 12:58:35 2024 +0000

    Add devcontainer.json with useful extensions

* Delete .devcontainer/devcontainer.json

* Update .gitignore with .devcontainer/

* Create styler.R

---------

Co-authored-by: Björn Grüning <[email protected]>
  • Loading branch information
kostrykin and bgruening authored Mar 8, 2024
1 parent 981998b commit c6d2a4f
Show file tree
Hide file tree
Showing 17 changed files with 271 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .github/styler.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env Rscript

library("argparse")
library("styler")

parser <- ArgumentParser(description = "Call styler")
parser$add_argument("dir",
metavar = "DIR", type = "character",
help = "File to parse"
)
parser$add_argument("--dry",
choices = c("off", "on"), default = "on"
)
args <- parser$parse_args()

file_info <- file.info(args$dir)
is_directory <- file_info$isdir

if (is_directory) {
captured_output <- capture.output({
result <- style_dir(args$dir, indent_by = 4, dry = args$dry, recursive = TRUE)
})
} else {
captured_output <- capture.output({
result <- style_file(args$dir, indent_by = 4, dry = args$dry)
})
}

n <- nrow(subset(result, changed == TRUE))
if (n > 0) {
if (args$dry == "off") {
print(paste("Changed", n, "files"))
} else {
stop(paste("Linting failed for", n, "files"))
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ template_shed.yml
/create_csvs.py
.DS_Store
._*
.devcontainer
8 changes: 8 additions & 0 deletions tools/morphological_operations/.shed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
categories:
- Imaging
description: Apply morphological operations to images
long_description: Apply morphological operations to images.
name: morphological_operations
owner: imgteam
homepage_url: https://github.com/bmcv
remote_repository_url: https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/morphological_operations
93 changes: 93 additions & 0 deletions tools/morphological_operations/morphological_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import argparse

import numpy as np
import scipy.ndimage as ndi
import skimage.io
import skimage.morphology as morph


def create_selem(args):
"""
Creates structuring element based on commandline arguments.
"""
assert args.selem_shape in (
'square',
'disk',
)

if args.selem_shape == 'square':
return np.ones((args.selem_size, args.selem_size))

elif args.selem_shape == 'disk':
return morph.disk(args.selem_size)


def apply_operation(args, im):
"""
Applies morphological operation to a 2-D single-channel image.
"""
assert im.ndim == 2
selem = create_selem(args)
values_count = len(np.unique(im))
if values_count <= 2:
im_proxy = np.zeros(im.shape, bool)
im_proxy[im == im.max()] = True
result_proxy = apply_binary_operation(args, im_proxy, selem)
result = np.full(im.shape, im.min(), im.dtype)
result[result_proxy] = im.max()
return result
else:
return apply_intensity_based_operation(args, im, selem)


def apply_intensity_based_operation(args, im, selem):
operations = {
'erosion': ndi.grey_erosion,
'dilation': ndi.grey_dilation,
'opening': ndi.grey_opening,
'closing': ndi.grey_closing,
}
if args.operation in operations:
operation = operations[args.operation]
return operation(input=im, structure=selem)
else:
raise ValueError(f'Operation "{args.operation}" not supported for this image type ({im.dtype}).')


def apply_binary_operation(args, im, selem):
operations = {
'erosion': ndi.binary_erosion,
'dilation': ndi.binary_dilation,
'opening': ndi.binary_opening,
'closing': ndi.binary_closing,
'fill_holes': ndi.binary_fill_holes,
}
operation = operations[args.operation]
return operation(input=im, structure=selem)


if __name__ == '__main__':

parser = argparse.ArgumentParser()
parser.add_argument('--operation', type=str)
parser.add_argument('--selem-shape', type=str)
parser.add_argument('--selem-size', type=int)
parser.add_argument('input', type=str)
parser.add_argument('output', type=str)
args = parser.parse_args()

im = skimage.io.imread(args.input)
assert im.ndim in (2, 3), 'Input image must be two-dimensional and either single-channel or multi-channel.'

if im.ndim == 2:
im_result = apply_operation(args, im)

else:
ch_result_list = []
for ch_idx in range(im.shape[2]):
ch = im[:, :, ch_idx]
ch_result = apply_operation(args, ch)
ch_result_list.append(ch_result)
im_result = np.dstack(ch_result_list)

skimage.io.imsave(args.output, im_result)
133 changes: 133 additions & 0 deletions tools/morphological_operations/morphological_operations.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<tool id="morphological_operations" name="Apply a morphological operation" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="23.0">
<description>with SciPy</description>
<macros>
<token name="@TOOL_VERSION@">1.12.0</token>
<token name="@VERSION_SUFFIX@">0</token>
</macros>
<edam_operations>
<edam_operation>operation_3443</edam_operation>
</edam_operations>
<xrefs>
<xref type="biii">scipy</xref>
</xrefs>
<requirements>
<requirement type="package" version="1.12.0">scipy</requirement>
<requirement type="package" version="0.22.0">scikit-image</requirement>
</requirements>
<command><![CDATA[
## Inputs
python '$__tool_directory__/morphological_operations.py' '$image'
--operation $operation
--selem-shape $selem_shape
--selem-size $selem_size
## Outputs
./result.tiff
]]>
</command>
<inputs>
<param name="image" type="data" format="png,tiff" label="Input image" />
<param label="Morphological operation" argument="--operation" type="select">
<option value="erosion" selected="True">Erosion</option>
<option value="dilation">Dilation</option>
<option value="opening">Opening</option>
<option value="closing">Closing</option>
<option value="fill_holes">Fill holes</option>
</param>
<param label="Structuring element" argument="--selem-shape" type="select">
<option value="disk" selected="True">Disk</option>
<option value="square">Square</option>
</param>
<param label="Structuring element size" argument="--selem-size" type="integer" min="1" max="256" value="1" help="The radius of the disk, or the width of the square, in pixels." />
</inputs>
<outputs>
<data format="tiff" name="result" from_work_dir="result.tiff" />
</outputs>
<tests>
<!-- Test binary operations (input images with two distinct values) -->
<test>
<param name="image" value="input1.tiff" />
<param name="operation" value="erosion" />
<param name="selem_shape" value="disk" />
<param name="selem_size" value="1" />
<output name="result" value="output1_erosion.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<test>
<param name="image" value="input1.tiff" />
<param name="operation" value="dilation" />
<param name="selem_shape" value="disk" />
<param name="selem_size" value="2" />
<output name="result" value="output1_dilation.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<test>
<param name="image" value="input1.tiff" />
<param name="operation" value="opening" />
<param name="selem_shape" value="square" />
<param name="selem_size" value="1" />
<output name="result" value="output1_opening.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<test>
<param name="image" value="input1.tiff" />
<param name="operation" value="closing" />
<param name="selem_shape" value="square" />
<param name="selem_size" value="2" />
<output name="result" value="output1_closing.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<!-- Test intensity-based operations (more than two distinct values) -->
<test>
<param name="image" value="input3.png" />
<param name="operation" value="erosion" />
<param name="selem_shape" value="disk" />
<param name="selem_size" value="1" />
<output name="result" value="output3_erosion.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<test>
<param name="image" value="input3.png" />
<param name="operation" value="dilation" />
<param name="selem_shape" value="disk" />
<param name="selem_size" value="2" />
<output name="result" value="output3_dilation.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<test>
<param name="image" value="input3.png" />
<param name="operation" value="opening" />
<param name="selem_shape" value="square" />
<param name="selem_size" value="1" />
<output name="result" value="output3_opening.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<test>
<param name="image" value="input3.png" />
<param name="operation" value="closing" />
<param name="selem_shape" value="square" />
<param name="selem_size" value="2" />
<output name="result" value="output3_closing.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
<!-- Test multi-channel image (with two distinct values per channel) -->
<test>
<param name="image" value="input2.png" />
<param name="operation" value="fill_holes" />
<param name="selem_shape" value="disk" />
<param name="selem_size" value="1" />
<output name="result" value="output2_fill_holes.tiff" ftype="tiff" compare="sim_size" delta="10" />
</test>
</tests>
<help>
Applies a morphological operation to a 2-D image.
For multi-channel images, the operation is applied to each channel separately.

The following operations are supported:

- **Erosion:** Shrink bright areas.
- **Dilation:** Grow bright areas.
- **Opening:** Erosion followed by Dilation. Erases tiny bright spots.
- **Closing:** Dilation followed by Erosion. Erases tiny dark holes.
- **Fill holes:** Fills the holes (dark areas) in binary images.
</help>
<citations>
<citation type="doi">10.1038/s41592-019-0686-2</citation>
</citations>
</tool>
Binary file not shown.
Binary file added tools/morphological_operations/test-data/input2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tools/morphological_operations/test-data/input3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit c6d2a4f

Please sign in to comment.