diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4b0cc076..542520a6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,12 +1,14 @@ -**For the contributor:** -* [ ] I have read the [CONTRIBUTING.md](https://github.com/BMCV/galaxy-image-analysis/blob/master/CONTRIBUTING.md) document. -* [ ] License permits unrestricted use (educational + commercial). -* [ ] This PR adds or updates a tool or tool collection. - * [ ] This PR adds a new tool or tool collection. - * [ ] This PR updates an existing tool or tool collection. - * [ ] Tools added/updated by this PR comply with the [Naming and Annotation Conventions for Tools in the Image Community in Galaxy](https://github.com/elixir-europe/biohackathon-projects-2023/blob/main/16/paper/paper.md#conventions) (or explain why they do not). -* [ ] This PR does something else (explain below). --- - +**FOR THE CONTRIBUTOR — Please fill out if applicable** + +Please make sure you have read the [CONTRIBUTING.md](https://github.com/BMCV/galaxy-image-analysis/blob/master/CONTRIBUTING.md) document (last updated: 2024/03/18). + +* [ ] License permits unrestricted use (educational + commercial). + +If this PR adds or updates a tool or tool collection: + +* [ ] This PR adds a new tool or tool collection. +* [ ] This PR updates an existing tool or tool collection. +* [ ] Tools added/updated by this PR comply with the [Naming and Annotation Conventions for Tools in the Image Community in Galaxy](https://github.com/elixir-europe/biohackathon-projects-2023/blob/main/16/paper/paper.md#conventions) (or explain why they do not). diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 95babe7d..173e3be2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,8 +6,8 @@ on: repository_dispatch: types: [run-all-tool-tests-command] env: - GALAXY_FORK: galaxyproject - GALAXY_BRANCH: release_23.2 + GALAXY_FORK: kostrykin # Temporary change to enable the features from https://github.com/galaxyproject/galaxy/pull/17556 and https://github.com/galaxyproject/galaxy/pull/17581 + GALAXY_BRANCH: galaxy-image-analysis # Temporary change to enable the features from https://github.com/galaxyproject/galaxy/pull/17556 and https://github.com/galaxyproject/galaxy/pull/17581 MAX_CHUNKS: 40 jobs: setup: @@ -96,6 +96,25 @@ jobs: with: path: ~/.cache/pip key: pip_cache_py_${{ matrix.python-version }}_gxy_${{ needs.setup.outputs.galaxy-head-sha }} +# +# --------------------------------------------------------- +# This is a temporary addition to enable the features from +# - https://github.com/galaxyproject/galaxy/pull/17556 +# - https://github.com/galaxyproject/galaxy/pull/17581 +# + - name: Planemo setup + uses: galaxyproject/planemo-ci-action@v1 + with: + mode: setup + repository-list: ${{ needs.setup.outputs.repository-list }} + tool-list: ${{ needs.setup.outputs.tool-list }} + additional-planemo-options: --biocontainers -s tests,output,inputs,help,general,command,citations,tool_xsd,xml_order,tool_urls,shed_metadata + - run: | + python -m pip install git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/util + python -m pip install git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/tool_util +# +# --------------------------------------------------------- +# - name: Planemo lint uses: galaxyproject/planemo-ci-action@v1 id: lint @@ -150,6 +169,28 @@ jobs: id: cpu-cores - name: Clean dotnet folder for space run: rm -Rf /usr/share/dotnet +# +# --------------------------------------------------------- +# This is a temporary addition to enable the features from +# - https://github.com/galaxyproject/galaxy/pull/17556 +# - https://github.com/galaxyproject/galaxy/pull/17581 +# + - name: Planemo setup + uses: galaxyproject/planemo-ci-action@v1 + with: + mode: setup + repository-list: ${{ needs.setup.outputs.repository-list }} + galaxy-fork: ${{ needs.setup.outputs.fork }} + galaxy-branch: ${{ needs.setup.outputs.branch }} + chunk: ${{ matrix.chunk }} + chunk-count: ${{ needs.setup.outputs.chunk-count }} + - run: | + python -m pip install git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/util + python -m pip install git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/tool_util + python -m pip install pillow tifffile +# +# --------------------------------------------------------- +# - name: Planemo test uses: galaxyproject/planemo-ci-action@v1 id: test diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 2fa59905..8c119537 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -16,8 +16,8 @@ on: - 'docs/**' - '*' env: - GALAXY_FORK: galaxyproject - GALAXY_BRANCH: release_23.2 + GALAXY_FORK: kostrykin # Temporary change to enable the features from https://github.com/galaxyproject/galaxy/pull/17556 and https://github.com/galaxyproject/galaxy/pull/17581 + GALAXY_BRANCH: galaxy-image-analysis # Temporary change to enable the features from https://github.com/galaxyproject/galaxy/pull/17556 and https://github.com/galaxyproject/galaxy/pull/17581 MAX_CHUNKS: 4 MAX_FILE_SIZE: 1M concurrency: @@ -133,6 +133,18 @@ jobs: if: ${{ github.event_name != 'pull_request' }} run: | echo "FAIL_LEVEL=error" >> "$GITHUB_ENV" +# +# --------------------------------------------------------- +# This is a temporary addition to enable the features from +# - https://github.com/galaxyproject/galaxy/pull/17556 +# - https://github.com/galaxyproject/galaxy/pull/17581 +# + - run: | + python -m pip install --no-dependencies git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/util + python -m pip install --no-dependencies git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/tool_util +# +# --------------------------------------------------------- +# - name: Planemo lint uses: galaxyproject/planemo-ci-action@v1 id: lint @@ -292,6 +304,19 @@ jobs: id: cpu-cores - name: Clean dotnet folder for space run: rm -Rf /usr/share/dotnet +# +# --------------------------------------------------------- +# This is a temporary addition to enable the features from +# - https://github.com/galaxyproject/galaxy/pull/17556 +# - https://github.com/galaxyproject/galaxy/pull/17581 +# + - run: | + python -m pip install --no-dependencies git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/util + python -m pip install --no-dependencies git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/tool_util + python -m pip install pillow tifffile +# +# --------------------------------------------------------- +# - name: Planemo test uses: galaxyproject/planemo-ci-action@v1 id: test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ad01aa8..69f42cd9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,97 @@ # Contributing -This document describes how to contribute to this repository. Pull -requests containing bug fixes, updates, and extensions to the existing -tools and tool suites in this repository will be considered for -inclusion. +This document is the attempt to collect some rough rules for tools to follow in this repository, to facilitate their consistency and interoperability. This document is an extension to the [Naming and Annotation Conventions for Tools in the Image Community in Galaxy](https://github.com/elixir-europe/biohackathon-projects-2023/blob/main/16/paper/paper.md#conventions) and compatibility should be maintained. This document is work in progress. -## How to Contribute +**How to Contribute:** * Make sure you have a [GitHub account](https://github.com/signup/free) * Make sure you have git [installed](https://help.github.com/articles/set-up-git) * Fork the repository on [GitHub](https://github.com/BMCV/galaxy-image-analysis/fork) * Make the desired modifications - consider using a [feature branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches). -* Try to stick to the [Conventions for Tools in the Image Community](https://github.com/elixir-europe/biohackathon-projects-2023/blob/main/16/conventions.md) and the [IUC standards](http://galaxy-iuc-standards.readthedocs.org/en/latest/) whenever you can +* Try to stick to the [Conventions for Tools in the Image Community](https://github.com/elixir-europe/biohackathon-projects-2023/blob/main/16/paper/paper.md#conventions) and the [IUC standards](http://galaxy-iuc-standards.readthedocs.org/en/latest/) whenever you can * Make sure you have added the necessary tests for your changes and they pass. * Open a [pull request](https://help.github.com/articles/using-pull-requests) with these changes. + +## Terminology + +**Label maps** are images with pixel-level annotations, usually corresponding to distinct image regions (e.g., objects). We avoid the terms *label image* and *labeled image*, since these can be easily confused with image-level annotations (instead of pixel-level). The labels (pixel values) must uniquely identify the labeled image regions (i.e. labels must be unique, even for non-adjacent image regions). If a label semantically corresponds to the image background, that label should be 0. + +**Binary images** are a special case of label maps with only two labels (e.g., image background and image foreground). To facilitate visual perception, the foreground label should correspond to white (value 255 for `uint8` images and value 65535 for `uint16` images), since background corresponds to the label 0, which is black. + +**Intensity images** are images which are *not* label maps (and thus neither binary images). + +## File types + +If a tool wrapper only supports single-channel 2-D images and uses a Python script, the structure of the input should be verified right after loading the image: + +```python +im = skimage.io.imread(args.input) +im = np.squeeze(im) # remove axes with length 1 +assert im.ndim == 2 +``` + +Tools with **label map inputs** should accept PNG and TIFF files. Tools with **label map outputs** should produce either `uint16` single-channel PNG or `uint16` single-channel TIFF. Using `uint8` instead of `uint16` is also acceptable, if there definetely are no more than 256 different labels. Using `uint8` should be preferred for binary images. + +> [!NOTE] +> It is a common misconception that PNG files must be RGB or RGBA, and that only `uint8` pixel values are supported. For example, the `cv2` module (OpenCV) can be used to create single-channel PNG files, or PNG files with `uint16` pixel values. Such files can then be read by `skimage.io.imread` without issues (however, `skimage.io.imwrite` seems not to be able to write such PNG files). + +Tools with **intensity image inputs** should accept PNG and TIFF files. Tools with **intensity image outputs** can be any data type and either PNG or TIFF. Image outputs meant for visualization (e.g., segmentation overlays, charts) should be PNG. + +## Testing + +### Testing infrastructure + +The support for the new [`image_diff` output verification method](https://docs.galaxyproject.org/en/latest/dev/schema.html#tool-tests-test-output) and [assertions for image data](https://docs.galaxyproject.org/en/latest/dev/schema.html#assertions-for-image-data) for Galaxy tool testing probably won't be available in Galaxy before 24.1 is released. + +Meanwhile, they are already available in the CI of the **galaxy-image-analyis** repostiroy! 🎉 https://github.com/BMCV/galaxy-image-analysis/pull/117 + +To also use them locally, you need to install the development versions of two Galaxy packages, pillow, and tifffile: +```python +python -m pip install git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/util +python -m pip install git+https://git@github.com/kostrykin/galaxy.git@galaxy-image-analysis#subdirectory=packages/tool_util +python -m pip install pillow tifffile +``` + +The [galaxy-image-analysis branch](https://github.com/kostrykin/galaxy/tree/galaxy-image-analysis) of the fork is the same as the [23.1 release of Galaxy](https://github.com/galaxyproject/galaxy/tree/release_23.1), plus the support for the image-based verification extensions. + +In addition, instead of running `planemo test`, you should use: +```python +planemo test --galaxy_source https://github.com/kostrykin/galaxy --galaxy_branch galaxy-image-analysis +``` +Linting with `planemo lint` works as usual. + +### Writing tests + +We recommend using macros for verification of image outputs. The macros are loaded as follows: +```xml + + tests.xml + +``` + +For testing of **binary image outputs** we recommend using the `mae` metric (mean absolute error). With the default value of `eps` of 0.01, this asserts that at most 1% of the image pixels are labeled differently: +```xml + +``` + +For testing of non-binary **label map outputs** with interchangeable labels, we recommend using the `iou` metric (one minus the *intersection over the union*). With the default value of `eps` of 0.01, this asserts that there is no labeled image region with an *intersection over the union* of less than 99%: +```xml + +``` +At the moment it is not possible to *pin* specific labels, for example to verify that the background is assigned the correct label, but this will hopefully be added in the future. + +For testing of **intensity image outputs** we recommend the `rms` metric (root mean square), because it is very sensitive to large pixel value differences, but tolerates smaller differences: +```xml + +``` +For `uint8` and `uint16` images, increasing the default value of `eps` to `1.0` should be tolerable, if required. + +## Future extensions + +Below is a list of open questions: + +- **How do we want to cope with multi-channel label maps?** For example, do or can we distinguish RGB labels from multi-channel binary masks, which are sometimes used to represent overlapping objects? + +- How can we distinguish multi-channel 2-D images from single-channel 3-D images? + +- How can we make clear to the user, whether a tool requires a 2-D image or also supports 3-D? diff --git a/README.md b/README.md index af58d5c3..3a764876 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ If Galaxy Image Analysis helped with the analysis of your data, please do not fo - [Process images using arithmetic expressions](https://usegalaxy.eu/root?tool_id=toolshed.g2.bx.psu.edu/repos/imgteam/image_math/image_math) with NumPy - [Scale image](https://usegalaxy.eu/root?tool_id=toolshed.g2.bx.psu.edu/repos/imgteam/scale_image/ip_scale_image) with scikit-image - [Show image info](https://usegalaxy.eu/root?tool_id=toolshed.g2.bx.psu.edu/repos/imgteam/image_info/ip_imageinfo) with Bioformats -- [Slice image](https://usegalaxy.eu/root?tool_id=toolshed.g2.bx.psu.edu/repos/imgteam/slice_image/ip_slice_image) +- [Slice image into patches](https://usegalaxy.eu/root?tool_id=toolshed.g2.bx.psu.edu/repos/imgteam/slice_image/ip_slice_image) - [Switch axis coordinates](https://usegalaxy.eu/root?tool_id=toolshed.g2.bx.psu.edu/repos/imgteam/imagecoordinates_flipaxis/imagecoordinates_flipaxis) ### Image conversion diff --git a/macros/creators.xml b/macros/creators.xml new file mode 100644 index 00000000..885c0711 --- /dev/null +++ b/macros/creators.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macros/creators/bmcv.xml b/macros/creators/bmcv.xml deleted file mode 100644 index 0fdcfe84..00000000 --- a/macros/creators/bmcv.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/macros/tests.xml b/macros/tests.xml new file mode 100644 index 00000000..e0853472 --- /dev/null +++ b/macros/tests.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/2d_auto_threshold/auto_threshold.py b/tools/2d_auto_threshold/auto_threshold.py index 71f5fabe..428547e7 100644 --- a/tools/2d_auto_threshold/auto_threshold.py +++ b/tools/2d_auto_threshold/auto_threshold.py @@ -27,23 +27,28 @@ } -def do_thresholding(in_fn, out_fn, th_method, block_size=5, threshold=0, invert_output=False): +def do_thresholding(in_fn, out_fn, th_method, block_size, offset, threshold, invert_output=False): img = skimage.io.imread(in_fn) - th = th_methods[th_method](img_raw=img, bz=block_size, thres=threshold) + img = np.squeeze(img) + assert img.ndim == 2 + + th = offset + th_methods[th_method](img_raw=img, bz=block_size, thres=threshold) res = img > th if invert_output: res = np.logical_not(res) + tifffile.imwrite(out_fn, skimage.util.img_as_ubyte(res)) if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Automatic Image Thresholding') + parser = argparse.ArgumentParser(description='Automatic image thresholding') parser.add_argument('im_in', help='Path to the input image') - parser.add_argument('im_out', help='Path to the output image (TIFF)') + parser.add_argument('im_out', help='Path to the output image (uint8)') parser.add_argument('th_method', choices=th_methods.keys(), help='Thresholding method') parser.add_argument('block_size', type=int, default=5, help='Odd size of pixel neighborhood for calculating the threshold') - parser.add_argument('threshold', type=float, default=0, help='Manual thresholding value') + parser.add_argument('offset', type=float, default=0, help='Offset of automatically determined threshold value') + parser.add_argument('threshold', type=float, default=0, help='Manual threshold value') parser.add_argument('--invert_output', default=False, action='store_true', help='Values below/above the threshold are labeled with 0/255 by default, and with 255/0 if this argument is used') args = parser.parse_args() - do_thresholding(args.im_in, args.im_out, args.th_method, args.block_size, args.threshold, args.invert_output) + do_thresholding(args.im_in, args.im_out, args.th_method, args.block_size, args.offset, args.threshold, args.invert_output) diff --git a/tools/2d_auto_threshold/auto_threshold.xml b/tools/2d_auto_threshold/auto_threshold.xml index cd686e52..02e5b247 100644 --- a/tools/2d_auto_threshold/auto_threshold.xml +++ b/tools/2d_auto_threshold/auto_threshold.xml @@ -1,9 +1,17 @@ with scikit-image + creators.xml + tests.xml 0.18.1 - 1 + 2 + + + + + + operation_3443 @@ -12,7 +20,7 @@ scikit-image - scikit-image + scikit-image tifffile @@ -22,6 +30,7 @@ ./out.tif '$th_method.method_id' '$th_method.block_size' + '$th_method.offset' '$th_method.threshold' $invert_output ]]> @@ -42,34 +51,42 @@ + + + + + + + + @@ -78,36 +95,50 @@ + - - + + + + + + + + + + - - + - + + + + + - Applies a standard thresholding algorithm to an image. + + **Applies a standard thresholding algorithm to a 2-D single-channel image. Yields a binary image.** The thresholding algorithm automatically determines a threshold value (unless manual thresholding is used). The input image is then thresholded, by assigning white (pixel value 255) to image regions above the determined threshold, and black (pixel value 0) to image regions below or equal to the determined threshold. The assignment of black and white to image regions below and above the threshold is inverted, if the corresponding option is set. + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/2d_auto_threshold/creators.xml b/tools/2d_auto_threshold/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/2d_auto_threshold/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/2d_auto_threshold/test-data/out.tif b/tools/2d_auto_threshold/test-data/out.tif deleted file mode 100644 index cdd6ea03..00000000 Binary files a/tools/2d_auto_threshold/test-data/out.tif and /dev/null differ diff --git a/tools/2d_auto_threshold/test-data/out1.tif b/tools/2d_auto_threshold/test-data/out1.tif new file mode 100644 index 00000000..d554139a Binary files /dev/null and b/tools/2d_auto_threshold/test-data/out1.tif differ diff --git a/tools/2d_auto_threshold/test-data/out2.tif b/tools/2d_auto_threshold/test-data/out2.tif index 85c97d2c..c449454b 100644 Binary files a/tools/2d_auto_threshold/test-data/out2.tif and b/tools/2d_auto_threshold/test-data/out2.tif differ diff --git a/tools/2d_auto_threshold/test-data/out3.tif b/tools/2d_auto_threshold/test-data/out3.tif index 3baffa63..85c97d2c 100644 Binary files a/tools/2d_auto_threshold/test-data/out3.tif and b/tools/2d_auto_threshold/test-data/out3.tif differ diff --git a/tools/2d_auto_threshold/test-data/out4.tif b/tools/2d_auto_threshold/test-data/out4.tif new file mode 100644 index 00000000..3baffa63 Binary files /dev/null and b/tools/2d_auto_threshold/test-data/out4.tif differ diff --git a/tools/2d_auto_threshold/test-data/rgb.png b/tools/2d_auto_threshold/test-data/rgb.png new file mode 100644 index 00000000..9f9a9170 Binary files /dev/null and b/tools/2d_auto_threshold/test-data/rgb.png differ diff --git a/tools/2d_auto_threshold/tests.xml b/tools/2d_auto_threshold/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/2d_auto_threshold/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/2d_feature_extraction/2d_feature_extraction.py b/tools/2d_feature_extraction/2d_feature_extraction.py index 3959b0e1..4b3e0baa 100644 --- a/tools/2d_feature_extraction/2d_feature_extraction.py +++ b/tools/2d_feature_extraction/2d_feature_extraction.py @@ -8,119 +8,120 @@ import skimage.morphology import skimage.segmentation -# TODO make importable by python script - -parser = argparse.ArgumentParser(description='Extract Features 2D') - -# TODO create factory for boilerplate code -features = parser.add_argument_group('compute features') -features.add_argument('--all', dest='all_features', action='store_true') -features.add_argument('--label', dest='add_label', action='store_true') -features.add_argument('--patches', dest='add_roi_patches', action='store_true') -features.add_argument('--max_intensity', dest='max_intensity', action='store_true') -features.add_argument('--mean_intensity', dest='mean_intensity', action='store_true') -features.add_argument('--min_intensity', dest='min_intensity', action='store_true') -features.add_argument('--moments_hu', dest='moments_hu', action='store_true') -features.add_argument('--centroid', dest='centroid', action='store_true') -features.add_argument('--bbox', dest='bbox', action='store_true') -features.add_argument('--area', dest='area', action='store_true') -features.add_argument('--filled_area', dest='filled_area', action='store_true') -features.add_argument('--convex_area', dest='convex_area', action='store_true') -features.add_argument('--perimeter', dest='perimeter', action='store_true') -features.add_argument('--extent', dest='extent', action='store_true') -features.add_argument('--eccentricity', dest='eccentricity', action='store_true') -features.add_argument('--equivalent_diameter', dest='equivalent_diameter', action='store_true') -features.add_argument('--euler_number', dest='euler_number', action='store_true') -features.add_argument('--inertia_tensor_eigvals', dest='inertia_tensor_eigvals', action='store_true') -features.add_argument('--major_axis_length', dest='major_axis_length', action='store_true') -features.add_argument('--minor_axis_length', dest='minor_axis_length', action='store_true') -features.add_argument('--orientation', dest='orientation', action='store_true') -features.add_argument('--solidity', dest='solidity', action='store_true') -features.add_argument('--moments', dest='moments', action='store_true') -features.add_argument('--convexity', dest='convexity', action='store_true') - -parser.add_argument('--label_file_binary', dest='label_file_binary', action='store_true') - -parser.add_argument('--raw', dest='raw_file', type=argparse.FileType('r'), - help='Original input file', required=False) -parser.add_argument('label_file', type=argparse.FileType('r'), - help='Label input file') -parser.add_argument('output_file', type=argparse.FileType('w'), - help='Tabular output file') -args = parser.parse_args() - -label_file_binary = args.label_file_binary -label_file = args.label_file.name -out_file = args.output_file.name -add_patch = args.add_roi_patches - -raw_image = None -if args.raw_file is not None: - raw_image = skimage.io.imread(args.raw_file.name) - -raw_label_image = skimage.io.imread(label_file) - -df = pd.DataFrame() -if label_file_binary: - raw_label_image = skimage.measure.label(raw_label_image) -regions = skimage.measure.regionprops(raw_label_image, intensity_image=raw_image) - -df['it'] = np.arange(len(regions)) - -if add_patch: - df['image'] = df['it'].map(lambda ait: regions[ait].image.astype(np.float).tolist()) - df['intensity_image'] = df['it'].map(lambda ait: regions[ait].intensity_image.astype(np.float).tolist()) - -# TODO no matrix features, but split in own rows? -if args.add_label or args.all_features: - df['label'] = df['it'].map(lambda ait: regions[ait].label) - -if raw_image is not None: - if args.max_intensity or args.all_features: - df['max_intensity'] = df['it'].map(lambda ait: regions[ait].max_intensity) - if args.mean_intensity or args.all_features: - df['mean_intensity'] = df['it'].map(lambda ait: regions[ait].mean_intensity) - if args.min_intensity or args.all_features: - df['min_intensity'] = df['it'].map(lambda ait: regions[ait].min_intensity) - if args.moments_hu or args.all_features: - df['moments_hu'] = df['it'].map(lambda ait: regions[ait].moments_hu) - -if args.centroid or args.all_features: - df['centroid'] = df['it'].map(lambda ait: regions[ait].centroid) -if args.bbox or args.all_features: - df['bbox'] = df['it'].map(lambda ait: regions[ait].bbox) -if args.area or args.all_features: - df['area'] = df['it'].map(lambda ait: regions[ait].area) -if args.filled_area or args.all_features: - df['filled_area'] = df['it'].map(lambda ait: regions[ait].filled_area) -if args.convex_area or args.all_features: - df['convex_area'] = df['it'].map(lambda ait: regions[ait].convex_area) -if args.perimeter or args.all_features: - df['perimeter'] = df['it'].map(lambda ait: regions[ait].perimeter) -if args.extent or args.all_features: - df['extent'] = df['it'].map(lambda ait: regions[ait].extent) -if args.eccentricity or args.all_features: - df['eccentricity'] = df['it'].map(lambda ait: regions[ait].eccentricity) -if args.equivalent_diameter or args.all_features: - df['equivalent_diameter'] = df['it'].map(lambda ait: regions[ait].equivalent_diameter) -if args.euler_number or args.all_features: - df['euler_number'] = df['it'].map(lambda ait: regions[ait].euler_number) -if args.inertia_tensor_eigvals or args.all_features: - df['inertia_tensor_eigvals'] = df['it'].map(lambda ait: regions[ait].inertia_tensor_eigvals) -if args.major_axis_length or args.all_features: - df['major_axis_length'] = df['it'].map(lambda ait: regions[ait].major_axis_length) -if args.minor_axis_length or args.all_features: - df['minor_axis_length'] = df['it'].map(lambda ait: regions[ait].minor_axis_length) -if args.orientation or args.all_features: - df['orientation'] = df['it'].map(lambda ait: regions[ait].orientation) -if args.solidity or args.all_features: - df['solidity'] = df['it'].map(lambda ait: regions[ait].solidity) -if args.moments or args.all_features: - df['moments'] = df['it'].map(lambda ait: regions[ait].moments) -if args.convexity or args.all_features: - perimeter = df['it'].map(lambda ait: regions[ait].perimeter) - area = df['it'].map(lambda ait: regions[ait].area) - df['convexity'] = area / (perimeter * perimeter) - -del df['it'] -df.to_csv(out_file, sep='\t', line_terminator='\n', index=False) + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='Extract image features') + + # TODO create factory for boilerplate code + features = parser.add_argument_group('compute features') + features.add_argument('--all', dest='all_features', action='store_true') + features.add_argument('--label', dest='add_label', action='store_true') + features.add_argument('--patches', dest='add_roi_patches', action='store_true') + features.add_argument('--max_intensity', dest='max_intensity', action='store_true') + features.add_argument('--mean_intensity', dest='mean_intensity', action='store_true') + features.add_argument('--min_intensity', dest='min_intensity', action='store_true') + features.add_argument('--moments_hu', dest='moments_hu', action='store_true') + features.add_argument('--centroid', dest='centroid', action='store_true') + features.add_argument('--bbox', dest='bbox', action='store_true') + features.add_argument('--area', dest='area', action='store_true') + features.add_argument('--filled_area', dest='filled_area', action='store_true') + features.add_argument('--convex_area', dest='convex_area', action='store_true') + features.add_argument('--perimeter', dest='perimeter', action='store_true') + features.add_argument('--extent', dest='extent', action='store_true') + features.add_argument('--eccentricity', dest='eccentricity', action='store_true') + features.add_argument('--equivalent_diameter', dest='equivalent_diameter', action='store_true') + features.add_argument('--euler_number', dest='euler_number', action='store_true') + features.add_argument('--inertia_tensor_eigvals', dest='inertia_tensor_eigvals', action='store_true') + features.add_argument('--major_axis_length', dest='major_axis_length', action='store_true') + features.add_argument('--minor_axis_length', dest='minor_axis_length', action='store_true') + features.add_argument('--orientation', dest='orientation', action='store_true') + features.add_argument('--solidity', dest='solidity', action='store_true') + features.add_argument('--moments', dest='moments', action='store_true') + features.add_argument('--convexity', dest='convexity', action='store_true') + + parser.add_argument('--label_file_binary', dest='label_file_binary', action='store_true') + + parser.add_argument('--raw', dest='raw_file', type=argparse.FileType('r'), + help='Original input file', required=False) + parser.add_argument('label_file', type=argparse.FileType('r'), + help='Label input file') + parser.add_argument('output_file', type=argparse.FileType('w'), + help='Tabular output file') + args = parser.parse_args() + + label_file_binary = args.label_file_binary + label_file = args.label_file.name + out_file = args.output_file.name + add_patch = args.add_roi_patches + + raw_image = None + if args.raw_file is not None: + raw_image = skimage.io.imread(args.raw_file.name) + + raw_label_image = skimage.io.imread(label_file) + + df = pd.DataFrame() + if label_file_binary: + raw_label_image = skimage.measure.label(raw_label_image) + regions = skimage.measure.regionprops(raw_label_image, intensity_image=raw_image) + + df['it'] = np.arange(len(regions)) + + if add_patch: + df['image'] = df['it'].map(lambda ait: regions[ait].image.astype(np.float).tolist()) + df['intensity_image'] = df['it'].map(lambda ait: regions[ait].intensity_image.astype(np.float).tolist()) + + # TODO no matrix features, but split in own rows? + if args.add_label or args.all_features: + df['label'] = df['it'].map(lambda ait: regions[ait].label) + + if raw_image is not None: + if args.max_intensity or args.all_features: + df['max_intensity'] = df['it'].map(lambda ait: regions[ait].max_intensity) + if args.mean_intensity or args.all_features: + df['mean_intensity'] = df['it'].map(lambda ait: regions[ait].mean_intensity) + if args.min_intensity or args.all_features: + df['min_intensity'] = df['it'].map(lambda ait: regions[ait].min_intensity) + if args.moments_hu or args.all_features: + df['moments_hu'] = df['it'].map(lambda ait: regions[ait].moments_hu) + + if args.centroid or args.all_features: + df['centroid'] = df['it'].map(lambda ait: regions[ait].centroid) + if args.bbox or args.all_features: + df['bbox'] = df['it'].map(lambda ait: regions[ait].bbox) + if args.area or args.all_features: + df['area'] = df['it'].map(lambda ait: regions[ait].area) + if args.filled_area or args.all_features: + df['filled_area'] = df['it'].map(lambda ait: regions[ait].filled_area) + if args.convex_area or args.all_features: + df['convex_area'] = df['it'].map(lambda ait: regions[ait].convex_area) + if args.perimeter or args.all_features: + df['perimeter'] = df['it'].map(lambda ait: regions[ait].perimeter) + if args.extent or args.all_features: + df['extent'] = df['it'].map(lambda ait: regions[ait].extent) + if args.eccentricity or args.all_features: + df['eccentricity'] = df['it'].map(lambda ait: regions[ait].eccentricity) + if args.equivalent_diameter or args.all_features: + df['equivalent_diameter'] = df['it'].map(lambda ait: regions[ait].equivalent_diameter) + if args.euler_number or args.all_features: + df['euler_number'] = df['it'].map(lambda ait: regions[ait].euler_number) + if args.inertia_tensor_eigvals or args.all_features: + df['inertia_tensor_eigvals'] = df['it'].map(lambda ait: regions[ait].inertia_tensor_eigvals) + if args.major_axis_length or args.all_features: + df['major_axis_length'] = df['it'].map(lambda ait: regions[ait].major_axis_length) + if args.minor_axis_length or args.all_features: + df['minor_axis_length'] = df['it'].map(lambda ait: regions[ait].minor_axis_length) + if args.orientation or args.all_features: + df['orientation'] = df['it'].map(lambda ait: regions[ait].orientation) + if args.solidity or args.all_features: + df['solidity'] = df['it'].map(lambda ait: regions[ait].solidity) + if args.moments or args.all_features: + df['moments'] = df['it'].map(lambda ait: regions[ait].moments) + if args.convexity or args.all_features: + perimeter = df['it'].map(lambda ait: regions[ait].perimeter) + area = df['it'].map(lambda ait: regions[ait].area) + df['convexity'] = area / (perimeter * perimeter) + + del df['it'] + df.to_csv(out_file, sep='\t', line_terminator='\n', index=False) diff --git a/tools/2d_feature_extraction/2d_feature_extraction.xml b/tools/2d_feature_extraction/2d_feature_extraction.xml index 9901aff6..ebbc8bb3 100644 --- a/tools/2d_feature_extraction/2d_feature_extraction.xml +++ b/tools/2d_feature_extraction/2d_feature_extraction.xml @@ -1,5 +1,13 @@ - + with scikit-image + + creators.xml + 0.14.2 + 0 + + + + operation_3443 @@ -8,8 +16,8 @@ scikit-image + scikit-image pandas - scikit-image numpy tifffile @@ -28,15 +36,15 @@ ]]> - + - - - + + + - + @@ -47,25 +55,25 @@ - + - + - - + + - - - - - + + + + + @@ -81,14 +89,19 @@ - + - **What it does** - This tool computes several features of a 2D label image and optionally more features using the original image. + **Computes features of a label map.** + + The computed features are computed based solely on the properties of the labels in the label map, + or, optionally, by also taking the intensities from a corresponding intensity image into account. + + The label map must be a 2-D or 3-D single-channel image. + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/2d_feature_extraction/creators.xml b/tools/2d_feature_extraction/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/2d_feature_extraction/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/2d_feature_extraction/test-data/input.tiff b/tools/2d_feature_extraction/test-data/input.tiff index 0bc85f46..91aea92a 100644 Binary files a/tools/2d_feature_extraction/test-data/input.tiff and b/tools/2d_feature_extraction/test-data/input.tiff differ diff --git a/tools/2d_feature_extraction/test-data/out.tsv b/tools/2d_feature_extraction/test-data/out.tsv index 0b7fa39d..2955497b 100644 --- a/tools/2d_feature_extraction/test-data/out.tsv +++ b/tools/2d_feature_extraction/test-data/out.tsv @@ -1,12 +1,3 @@ -area -612 -612 -375 -375 -729 -729 -399 -399 -3 -3 -434042 +label area +1 8 +2 9 diff --git a/tools/2d_filter_segmentation_by_features/2d_filter_segmentation_by_features.xml b/tools/2d_filter_segmentation_by_features/2d_filter_segmentation_by_features.xml index 50ca2519..8e6c3d67 100644 --- a/tools/2d_filter_segmentation_by_features/2d_filter_segmentation_by_features.xml +++ b/tools/2d_filter_segmentation_by_features/2d_filter_segmentation_by_features.xml @@ -1,5 +1,12 @@ - - + + + + creators.xml + tests.xml + + + + operation_3443 @@ -12,15 +19,20 @@ pandas tifffile - - - + - - - + + + @@ -30,12 +42,45 @@ - + - Filter label image by rules (e.g., remove large or deformed objects). - Rules file has a specific format (cols: ,f1,2, rows: feature_name, min, max). The features have to be also profived in a specific format (cols: label, f1, f2). + + **Filters a label map by rules (e.g., remove large or deformed objects).** + + The properties of the labeled image regions (features) must be provided in a specific tabular format + (columns: ``label``, ``feature1``, ``feature2``, and so on, where ``feature1`` and ``feature2`` can be arbitrary strings). + Each row corresponds to a labeled image region. + An example is given below. + + +-------+-------+---------------------+ + | label | area | eccentricity | + +-------+-------+---------------------+ + | 1 | 344 | 0.42521053699241596 | + +-------+-------+---------------------+ + | 2 | 434 | 0.47679001553231926 | + +-------+-------+---------------------+ + | 3 | 907 | 0.9973539531125177 | + +-------+-------+---------------------+ + | 4 | 14320 | 0.17131009631035327 | + +-------+-------+---------------------+ + + The rules also must be supplied in a specific tabular format with three rows. + The top-left cell is empty, and the rest of the first row corresponds to the feature names (such as ``feature1`` or ``feature2``, see above). + The rest of the first column corresponds to the two values ``min`` and ``max``. + Each of the rows defines the minimum and maximum values for the corresponding features. + A labeled image region is retrained if and only if it passes all checks with repsect to the given ``min`` and ``max`` values. + An example is given below. + + +-----+--------+--------------+ + | | area | eccentricity | + +-----+--------+--------------+ + | min | 500 | 0. | + +-----+--------+--------------+ + | max | 100000 | 0.5 | + +-----+--------+--------------+ + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/2d_filter_segmentation_by_features/creators.xml b/tools/2d_filter_segmentation_by_features/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/2d_filter_segmentation_by_features/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/2d_filter_segmentation_by_features/tests.xml b/tools/2d_filter_segmentation_by_features/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/2d_filter_segmentation_by_features/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/2d_histogram_equalization/creators.xml b/tools/2d_histogram_equalization/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/2d_histogram_equalization/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/2d_histogram_equalization/histogram_equalization.xml b/tools/2d_histogram_equalization/histogram_equalization.xml index 52e708dc..d41e668e 100644 --- a/tools/2d_histogram_equalization/histogram_equalization.xml +++ b/tools/2d_histogram_equalization/histogram_equalization.xml @@ -1,13 +1,23 @@ - - with scikit-image + + with scikit-image + + creators.xml + tests.xml + 0.14.2 + 0 + + + + operation_3443 - galaxy_image_analysis + scikit-image + scikit-image - scikit-image + scikit-image numpy pillow tifffile @@ -18,8 +28,8 @@ ]]> - - + + @@ -30,12 +40,14 @@ - + - Applies histogram equalization to an image. + + **Applies histogram equalization to an image.** + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/2d_histogram_equalization/tests.xml b/tools/2d_histogram_equalization/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/2d_histogram_equalization/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/2d_simple_filter/creators.xml b/tools/2d_simple_filter/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/2d_simple_filter/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/2d_simple_filter/filter.xml b/tools/2d_simple_filter/filter.xml index 9192c42c..868930e8 100644 --- a/tools/2d_simple_filter/filter.xml +++ b/tools/2d_simple_filter/filter.xml @@ -1,49 +1,135 @@ - - with scikit-image + + with scipy + + creators.xml + tests.xml + 1.12.0 + 0 + + + + operation_3443 - scikit-image - scikit-image + scipy - scikit-image - numpy - pillow - tifffile + scipy + numpy + scikit-image + tifffile - - - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - **What it does** - Applies a standard filter to an image. + **Applies a standard filter to a single-channel 2-D image.** + + Mean filters like the Gaussian filter or the median filter preserve both the brightness of the image, and the range of values. + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/2d_simple_filter/filter_image.py b/tools/2d_simple_filter/filter_image.py index ac2aa5dd..865b267e 100644 --- a/tools/2d_simple_filter/filter_image.py +++ b/tools/2d_simple_filter/filter_image.py @@ -1,29 +1,28 @@ import argparse -import sys -import skimage.filters +import scipy.ndimage as ndi import skimage.io import skimage.util -from skimage import img_as_uint from skimage.morphology import disk -filterOptions = { - 'median': lambda img_raw, radius: skimage.filters.median(img_raw, disk(radius)), - 'gaussian': lambda img_raw, radius: skimage.filters.gaussian(img_raw, sigma=radius), - 'prewitt': lambda img_raw, radius: skimage.filters.prewitt(img_raw), - 'sobel': lambda img_raw, radius: skimage.filters.sobel(img_raw), - 'scharr': lambda img_raw, radius: skimage.filters.scharr(img_raw), +filters = { + 'gaussian': lambda im, sigma: ndi.gaussian_filter(im, sigma), + 'median': lambda im, radius: ndi.median_filter(im, footprint=disk(radius)), + 'prewitt_h': lambda im, *args: ndi.prewitt(im, axis=1), + 'prewitt_v': lambda im, *args: ndi.prewitt(im, axis=0), + 'sobel_h': lambda im, *args: ndi.sobel(im, axis=1), + 'sobel_v': lambda im, *args: ndi.sobel(im, axis=0), } if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('input_file', type=argparse.FileType('r'), default=sys.stdin, help='input file') - parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin, help='out file (TIFF)') - parser.add_argument('filter_type', choices=filterOptions.keys(), help='conversion type') - parser.add_argument('radius', default=3.0, type=float, help='Radius/Sigma') + parser.add_argument('input', type=argparse.FileType('r'), help='Input file') + parser.add_argument('output', type=argparse.FileType('w'), help='Output file (TIFF)') + parser.add_argument('filter', choices=filters.keys(), help='Filter to be used') + parser.add_argument('size', type=float, help='Size of the filter (e.g., radius, sigma)') args = parser.parse_args() - img_in = skimage.io.imread(args.input_file.name) - res = img_as_uint(filterOptions[args.filter_type](img_in, args.radius)) - skimage.io.imsave(args.out_file.name, res, plugin='tifffile') + im = skimage.io.imread(args.input.name) + res = filters[args.filter](im, args.size) + skimage.io.imsave(args.output.name, res, plugin='tifffile') diff --git a/tools/2d_simple_filter/test-data/input1_gaussian.tif b/tools/2d_simple_filter/test-data/input1_gaussian.tif new file mode 100644 index 00000000..fb68df02 Binary files /dev/null and b/tools/2d_simple_filter/test-data/input1_gaussian.tif differ diff --git a/tools/2d_simple_filter/test-data/input1_median.tif b/tools/2d_simple_filter/test-data/input1_median.tif new file mode 100644 index 00000000..fc1cec84 Binary files /dev/null and b/tools/2d_simple_filter/test-data/input1_median.tif differ diff --git a/tools/2d_simple_filter/test-data/input1_prewitt_h.tif b/tools/2d_simple_filter/test-data/input1_prewitt_h.tif new file mode 100644 index 00000000..eabc7186 Binary files /dev/null and b/tools/2d_simple_filter/test-data/input1_prewitt_h.tif differ diff --git a/tools/2d_simple_filter/test-data/input1_prewitt_v.tif b/tools/2d_simple_filter/test-data/input1_prewitt_v.tif new file mode 100644 index 00000000..d93ee84b Binary files /dev/null and b/tools/2d_simple_filter/test-data/input1_prewitt_v.tif differ diff --git a/tools/2d_simple_filter/test-data/input1_sobel_h.tif b/tools/2d_simple_filter/test-data/input1_sobel_h.tif new file mode 100644 index 00000000..1b9d4713 Binary files /dev/null and b/tools/2d_simple_filter/test-data/input1_sobel_h.tif differ diff --git a/tools/2d_simple_filter/test-data/input1_sobel_v.tif b/tools/2d_simple_filter/test-data/input1_sobel_v.tif new file mode 100644 index 00000000..47510c23 Binary files /dev/null and b/tools/2d_simple_filter/test-data/input1_sobel_v.tif differ diff --git a/tools/2d_simple_filter/test-data/sample.tif b/tools/2d_simple_filter/test-data/input1_uint8.tif similarity index 100% rename from tools/2d_simple_filter/test-data/sample.tif rename to tools/2d_simple_filter/test-data/input1_uint8.tif diff --git a/tools/2d_simple_filter/test-data/input2_float.tif b/tools/2d_simple_filter/test-data/input2_float.tif new file mode 100644 index 00000000..65b23761 Binary files /dev/null and b/tools/2d_simple_filter/test-data/input2_float.tif differ diff --git a/tools/2d_simple_filter/test-data/input2_gaussian.tif b/tools/2d_simple_filter/test-data/input2_gaussian.tif new file mode 100644 index 00000000..9852c0c9 Binary files /dev/null and b/tools/2d_simple_filter/test-data/input2_gaussian.tif differ diff --git a/tools/2d_simple_filter/test-data/res.tif b/tools/2d_simple_filter/test-data/res.tif deleted file mode 100644 index 629c338e..00000000 Binary files a/tools/2d_simple_filter/test-data/res.tif and /dev/null differ diff --git a/tools/2d_simple_filter/tests.xml b/tools/2d_simple_filter/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/2d_simple_filter/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/anisotropic_diffusion/anisotropic_diffusion.xml b/tools/anisotropic_diffusion/anisotropic_diffusion.xml index e80a0bde..2dab21c5 100644 --- a/tools/anisotropic_diffusion/anisotropic_diffusion.xml +++ b/tools/anisotropic_diffusion/anisotropic_diffusion.xml @@ -1,13 +1,21 @@ - + with MedPy + + creators.xml + tests.xml + 0.4.0 + 0 + + + + operation_3443 - scikit-image - medpy - numpy - tifffile + medpy + numpy + scikit-image - + - - - + + + @@ -34,13 +42,13 @@ - + - **What it does** - Edge-preserving, Anisotropic image diffusion + **Applies edge-preserving, anisotropic image diffusion.** + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/anisotropic_diffusion/creators.xml b/tools/anisotropic_diffusion/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/anisotropic_diffusion/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/anisotropic_diffusion/tests.xml b/tools/anisotropic_diffusion/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/anisotropic_diffusion/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/bfconvert/bfconvert.xml b/tools/bfconvert/bfconvert.xml index aa62b17f..224a973d 100644 --- a/tools/bfconvert/bfconvert.xml +++ b/tools/bfconvert/bfconvert.xml @@ -1,15 +1,25 @@ - - with Bioformats - - operation_3443 - - - python-bioformats - - - bftools - - + + with Bioformats + + creators.xml + tests.xml + 6.7.0 + 3 + + + + + + + operation_3443 + + + python-bioformats + + + bftools + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -182,18 +192,20 @@ - + - + - - - **What it does** + + + + **Converts image format.** + + Universial image converter using bioformats. - Universial image converter using bioformats. 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/bfconvert/creators.xml b/tools/bfconvert/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/bfconvert/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/bfconvert/test-data/res.ome.tiff b/tools/bfconvert/test-data/res.ome.tiff index a2f3def8..ec651d2b 100644 Binary files a/tools/bfconvert/test-data/res.ome.tiff and b/tools/bfconvert/test-data/res.ome.tiff differ diff --git a/tools/bfconvert/test-data/res.tiff b/tools/bfconvert/test-data/res.tiff index 3cd3d73e..77d4b284 100644 Binary files a/tools/bfconvert/test-data/res.tiff and b/tools/bfconvert/test-data/res.tiff differ diff --git a/tools/bfconvert/tests.xml b/tools/bfconvert/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/bfconvert/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/bioformats2raw/bf2raw.xml b/tools/bioformats2raw/bf2raw.xml index 75910ae3..4bc6b176 100644 --- a/tools/bioformats2raw/bf2raw.xml +++ b/tools/bioformats2raw/bf2raw.xml @@ -1,9 +1,13 @@ with Bioformats - 0.7.0 - 2 + creators.xml + 0.7.0 + 3 + + + operation_3443 @@ -119,11 +123,14 @@ $bf2raw_params.droptop -**What it does** -Bioformats-based tool that converts a wide range of image file formats to the cloud-optimised format OME-Zarr following the `OME-NGFF`_ specification. + **Converts images to OME-Zarr.** + + Bioformats-based tool that converts a wide range of image file formats to the cloud-optimised format + OME-Zarr following the `OME-NGFF`_ specification. + + .. _OME-NGFF: https://ngff.openmicroscopy.org/latest -.. _OME-NGFF: https://ngff.openmicroscopy.org/latest https://doi.org/10.5281/zenodo.5548102 diff --git a/tools/bioformats2raw/creators.xml b/tools/bioformats2raw/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/bioformats2raw/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/colorize_labels/colorize_labels.xml b/tools/colorize_labels/colorize_labels.xml index 7c6e8378..7985bd5e 100644 --- a/tools/colorize_labels/colorize_labels.xml +++ b/tools/colorize_labels/colorize_labels.xml @@ -1,14 +1,19 @@ with NetworkX + creators.xml + tests.xml 3.2.1 - 0 + 1 + + + operation_3443 - networkx + networkx numpy scikit-image @@ -42,18 +47,23 @@ - + + + - + + + - Colorize a 2-D label map for visualization using greedy coloring. + + **Colorizes a 2-D label map for visualization using greedy coloring.** Label maps are produced by segmentation and other image analysis steps. Direct inspection of label maps can be difficult, @@ -61,6 +71,7 @@ distinguish visually from each other and from the image background. To facilitate the visual inspection of label maps, this tools converts label maps to color images, by assigning each label a unique color. + diff --git a/tools/colorize_labels/creators.xml b/tools/colorize_labels/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/colorize_labels/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/colorize_labels/tests.xml b/tools/colorize_labels/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/colorize_labels/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/concat_channels/concat_channels.py b/tools/concat_channels/concat_channels.py index 903092f9..da1bd57c 100644 --- a/tools/concat_channels/concat_channels.py +++ b/tools/concat_channels/concat_channels.py @@ -1,33 +1,42 @@ import argparse -import warnings import numpy as np import skimage.io import skimage.util -def concat_channels(input_image_paths, output_image_path, axis): +def concat_channels(input_image_paths, output_image_path, axis, preserve_values): images = [] for image_path in input_image_paths: + raw_image = skimage.io.imread(image_path) if len(raw_image.shape) == 2: if axis == 0: raw_image = [raw_image] else: raw_image = np.expand_dims(raw_image, 2) + + # Preserve values: Convert to `float` dtype without changing the values + if preserve_values: + raw_image = raw_image.astype(float) + + # Preserve brightness: Scale values to 0..1 + else: + raw_image = skimage.util.img_as_float(raw_image) + images.append(raw_image) + + # Do the concatenation and save res = np.concatenate(images, axis) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - res = skimage.util.img_as_uint(res) # Attention: precision loss - skimage.io.imsave(output_image_path, res, plugin='tifffile') + skimage.io.imsave(output_image_path, res, plugin='tifffile') if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('input_files', type=argparse.FileType('r'), nargs='+', help='input file') - parser.add_argument('-o', dest='out_file', type=argparse.FileType('w'), help='out file (TIFF)') - parser.add_argument('--axis', dest='axis', type=int, default=0, choices=[0, 2], help='concatenation axis') + parser.add_argument('input_files', type=argparse.FileType('r'), nargs='+') + parser.add_argument('-o', dest='out_file', type=argparse.FileType('w')) + parser.add_argument('--axis', dest='axis', type=int, default=0, choices=[0, 2]) + parser.add_argument('--preserve_values', default=False, action='store_true') args = parser.parse_args() - concat_channels([x.name for x in args.input_files], args.out_file.name, args.axis) + concat_channels([x.name for x in args.input_files], args.out_file.name, args.axis, args.preserve_values) diff --git a/tools/concat_channels/concat_channels.xml b/tools/concat_channels/concat_channels.xml index 0269a885..88c07e67 100644 --- a/tools/concat_channels/concat_channels.xml +++ b/tools/concat_channels/concat_channels.xml @@ -1,5 +1,12 @@ - + + + creators.xml + tests.xml + + + + operation_3443 @@ -7,40 +14,71 @@ galaxy_image_analysis - scikit-image - numpy - tifffile + scikit-image + numpy + tifffile - - - + + ]]> - - - - + + + + + + + + + + + + + - + - + + + + + - **What it does** - This tool concatenates images. + **Concatenates images along arbitrary axes.** + + This can be used, for example, to spatially concatenate images, or along their channels. + + This tool either preserves the image brightness, or the range of values. + In general, both cannot be preserved when concatenating images of different pixel types (e.g., uint8 and uint16). + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/concat_channels/creators.xml b/tools/concat_channels/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/concat_channels/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/concat_channels/test-data/input1_uint8.png b/tools/concat_channels/test-data/input1_uint8.png new file mode 100644 index 00000000..26452e17 Binary files /dev/null and b/tools/concat_channels/test-data/input1_uint8.png differ diff --git a/tools/concat_channels/test-data/input2_float.tiff b/tools/concat_channels/test-data/input2_float.tiff new file mode 100644 index 00000000..4809551e Binary files /dev/null and b/tools/concat_channels/test-data/input2_float.tiff differ diff --git a/tools/concat_channels/test-data/out.tiff b/tools/concat_channels/test-data/out.tiff deleted file mode 100644 index 0bc85f46..00000000 Binary files a/tools/concat_channels/test-data/out.tiff and /dev/null differ diff --git a/tools/concat_channels/test-data/res.tiff b/tools/concat_channels/test-data/res.tiff deleted file mode 100644 index 7c31f754..00000000 Binary files a/tools/concat_channels/test-data/res.tiff and /dev/null differ diff --git a/tools/concat_channels/test-data/res_preserve_brightness.tiff b/tools/concat_channels/test-data/res_preserve_brightness.tiff new file mode 100644 index 00000000..a47e8a56 Binary files /dev/null and b/tools/concat_channels/test-data/res_preserve_brightness.tiff differ diff --git a/tools/concat_channels/test-data/res_preserve_values.tiff b/tools/concat_channels/test-data/res_preserve_values.tiff new file mode 100644 index 00000000..7adb27b7 Binary files /dev/null and b/tools/concat_channels/test-data/res_preserve_values.tiff differ diff --git a/tools/concat_channels/test-data/sample1.png b/tools/concat_channels/test-data/sample1.png deleted file mode 100644 index 2283a3b6..00000000 Binary files a/tools/concat_channels/test-data/sample1.png and /dev/null differ diff --git a/tools/concat_channels/test-data/sample2.png b/tools/concat_channels/test-data/sample2.png deleted file mode 100644 index 95d78dc6..00000000 Binary files a/tools/concat_channels/test-data/sample2.png and /dev/null differ diff --git a/tools/concat_channels/tests.xml b/tools/concat_channels/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/concat_channels/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/image_math/creators.xml b/tools/image_math/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/image_math/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/image_math/image_math.xml b/tools/image_math/image_math.xml index 98576240..6c0adade 100644 --- a/tools/image_math/image_math.xml +++ b/tools/image_math/image_math.xml @@ -1,14 +1,19 @@ with NumPy + creators.xml + tests.xml 1.26.4 - 0 + 1 + + + operation_3443 - numpy + numpy scikit-image - + @@ -58,7 +63,7 @@ - + @@ -71,7 +76,7 @@ - + @@ -84,7 +89,7 @@ - + @@ -93,12 +98,12 @@ - + - This tool processes images according to pixel-wise arithmetic expressions. + **Processes images according to pixel-wise arithmetic expressions.** The supported pixel-wise expressions are: @@ -128,4 +133,4 @@ 10.1038/s41586-020-2649-2 - \ No newline at end of file + diff --git a/tools/image_math/tests.xml b/tools/image_math/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/image_math/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/morphological_operations/creators.xml b/tools/morphological_operations/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/morphological_operations/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/morphological_operations/morphological_operations.xml b/tools/morphological_operations/morphological_operations.xml index 331e107c..5631e6eb 100644 --- a/tools/morphological_operations/morphological_operations.xml +++ b/tools/morphological_operations/morphological_operations.xml @@ -1,9 +1,14 @@ with SciPy + creators.xml + tests.xml 1.12.0 0 + + + operation_3443 @@ -11,7 +16,7 @@ scipy - scipy + scipy scikit-image - + - + - + - + @@ -83,28 +88,28 @@ - + - + - + - + @@ -112,11 +117,13 @@ - + - Applies a morphological operation to a 2-D image. + + **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: @@ -126,8 +133,9 @@ - **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. + 10.1038/s41592-019-0686-2 - \ No newline at end of file + diff --git a/tools/morphological_operations/tests.xml b/tools/morphological_operations/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/morphological_operations/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/orientationpy/creators.xml b/tools/orientationpy/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/orientationpy/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/orientationpy/orientationpy.xml b/tools/orientationpy/orientationpy.xml index 106f4c4a..9585c8bf 100644 --- a/tools/orientationpy/orientationpy.xml +++ b/tools/orientationpy/orientationpy.xml @@ -1,9 +1,13 @@ with OrientationPy + creators.xml 0.2.0.4 - 0 + 1 + + + operation_3443 @@ -11,7 +15,7 @@ orientationj - orientationpy + orientationpy scikit-image - Compute the orientation of 2-D images in degrees using OrientationPy. + + **Computes the orientation of 2-D images in degrees using OrientationPy.** OrientationPy is the pythonic successor to the well-loved OrientationJ Fiji Plugin. It is a library that takes in images and computes the orientation of the greylevels. A key step is the computation of image gradients, for a number of different techniques is supported. For more information on ``--min_energy`` and ``--min_coherency`` see: https://epfl-center-for-imaging.gitlab.io/orientationpy/orientationpy_examples/plot_fibres_2d.html#plot-hsv-composition + 10.1007/s10237-011-0325-z diff --git a/tools/overlay_images/creators.xml b/tools/overlay_images/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/overlay_images/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/overlay_images/overlay_images.xml b/tools/overlay_images/overlay_images.xml index c1ae6f0a..71069978 100644 --- a/tools/overlay_images/overlay_images.xml +++ b/tools/overlay_images/overlay_images.xml @@ -1,9 +1,15 @@ + creators.xml + tests.xml 0.0.4 - 0 + 1 + + + + operation_3443 @@ -38,10 +44,10 @@ - + - - + + @@ -53,7 +59,7 @@ - + @@ -75,14 +81,14 @@ - + - + @@ -91,15 +97,15 @@ - + - + - + @@ -107,14 +113,26 @@ - + + + + + + + + + - - - + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/overlay_images/test-data/sample1_uint8_rgb.png b/tools/overlay_images/test-data/sample1_uint8_rgb.png new file mode 100644 index 00000000..e867cfc5 Binary files /dev/null and b/tools/overlay_images/test-data/sample1_uint8_rgb.png differ diff --git a/tools/overlay_images/test-data/test4.png b/tools/overlay_images/test-data/test4.png index c09c7c0b..bf77aa8c 100644 Binary files a/tools/overlay_images/test-data/test4.png and b/tools/overlay_images/test-data/test4.png differ diff --git a/tools/overlay_images/test-data/test5.png b/tools/overlay_images/test-data/test5.png new file mode 100644 index 00000000..a59f48fc Binary files /dev/null and b/tools/overlay_images/test-data/test5.png differ diff --git a/tools/overlay_images/tests.xml b/tools/overlay_images/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/overlay_images/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/projective_transformation/creators.xml b/tools/projective_transformation/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/projective_transformation/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/projective_transformation/projective_transformation.xml b/tools/projective_transformation/projective_transformation.xml index 8f29bc1d..d164e9bb 100644 --- a/tools/projective_transformation/projective_transformation.xml +++ b/tools/projective_transformation/projective_transformation.xml @@ -1,5 +1,14 @@ - + + + creators.xml + tests.xml + 0.1.2 + 3 + + + + operation_3443 @@ -7,11 +16,11 @@ galaxy_image_analysis - scikit-image + scikit-image pandas - numpy - scipy - tifffile + numpy + scipy + tifffile - - - + + + @@ -37,27 +46,28 @@ - + - + - + -**What it does** + **Performs projective transformation.** + + This tool performs a projective transformation of 2-D image into the coordinate system of another 2-D image. + Multi-channel images are supported (e.g., RGB). - This tool performs a projective transformation of the moving 2D image so that it fits the fixed 2D image. - Multi-channel and RGB images are supported. 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/projective_transformation/test-data/moving_rgb_warped.png b/tools/projective_transformation/test-data/moving_rgb_warped.png index 9bbdd1da..45ed3bdd 100644 Binary files a/tools/projective_transformation/test-data/moving_rgb_warped.png and b/tools/projective_transformation/test-data/moving_rgb_warped.png differ diff --git a/tools/projective_transformation/test-data/moving_warped.png b/tools/projective_transformation/test-data/moving_warped.png index 04f9b25e..780e4c53 100644 Binary files a/tools/projective_transformation/test-data/moving_warped.png and b/tools/projective_transformation/test-data/moving_warped.png differ diff --git a/tools/projective_transformation/test-data/multi_f.tif b/tools/projective_transformation/test-data/multi_f.tif index 6787941d..d8041164 100644 Binary files a/tools/projective_transformation/test-data/multi_f.tif and b/tools/projective_transformation/test-data/multi_f.tif differ diff --git a/tools/projective_transformation/test-data/multi_m.tif b/tools/projective_transformation/test-data/multi_m.tif index 92220e89..505ddf9f 100644 Binary files a/tools/projective_transformation/test-data/multi_m.tif and b/tools/projective_transformation/test-data/multi_m.tif differ diff --git a/tools/projective_transformation/test-data/multi_m_warped.tif b/tools/projective_transformation/test-data/multi_m_warped.tif index c6d9019a..7bd932b4 100644 Binary files a/tools/projective_transformation/test-data/multi_m_warped.tif and b/tools/projective_transformation/test-data/multi_m_warped.tif differ diff --git a/tools/projective_transformation/tests.xml b/tools/projective_transformation/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/projective_transformation/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/projective_transformation_points/creators.xml b/tools/projective_transformation_points/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/projective_transformation_points/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/projective_transformation_points/projective_transformation_points.xml b/tools/projective_transformation_points/projective_transformation_points.xml index 31aaba4c..c27eaec6 100644 --- a/tools/projective_transformation_points/projective_transformation_points.xml +++ b/tools/projective_transformation_points/projective_transformation_points.xml @@ -1,5 +1,13 @@ - + + + creators.xml + 0.1.1 + 3 + + + + operation_3443 @@ -22,8 +30,8 @@ ]]> - - + + @@ -36,15 +44,20 @@ - **What it does** - This tool performs a projective transformation of regions of interest (ROIs) defined by pixel (point) coordinates with/without labels. + **Performs projective transformation of point coordinates.** - About the format of point coordinates (and labels) in the input TSV table: - 1st column: x-coordinate; - 2nd column: y-coordinate; - (optional) more column(s): point label(s) (numerical or categorical). - The top row of the table will be regarded as column headers. + This tool performs a projective transformation of point coordinates with/without labels. + + The input point coordinates to be transformed must be in the following format: + + **Column 1:** The horizontal coordinate. + + **Column 2:** The vertical coordinate. + + **Further columns:** Point labels (numerical or categorical, optional). + + The frist row of the table will be regarded as column headers. diff --git a/tools/rfove/creators.xml b/tools/rfove/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/rfove/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/rfove/rfove.xml b/tools/rfove/rfove.xml index f5569ede..d054ec96 100644 --- a/tools/rfove/rfove.xml +++ b/tools/rfove/rfove.xml @@ -1,9 +1,14 @@ with RFOVE + creators.xml + tests.xml 2023.11.12 - 1 + 2 + + + operation_3443 @@ -27,7 +32,7 @@ ]]> - + @@ -40,11 +45,20 @@ - + - RFOVE is completely unsupervised, operates without any assumption or prior knowledge on the object’s shape and extends and improves the Decremental Ellipse Fitting Algorithm (DEFA). Both RFOVE and DEFA solve the multi-ellipse fitting problem by performing model selection that is guided by the minimization of the Akaike Information Criterion on a suitably defined shape complexity measure. However, in contrast to DEFA, RFOVE minimizes an objective function that allows for ellipses with higher degree of overlap and, thus, achieves better ellipse-based shape approximation. + + **Perform segmentation using shape decomposition based on elliptical models and expectation maximization.** + + RFOVE is completely unsupervised, operates without any assumption or prior knowledge on the object's shape + and extends and improves the *Decremental Ellipse Fitting Algorithm* (DEFA). Both RFOVE and DEFA solve the + multi-ellipse fitting problem by performing model selection that is guided by the minimization of the + *Akaike Information Criterion* on a suitably defined shape complexity measure. However, in contrast to + DEFA, RFOVE minimizes an objective function that allows for ellipses with higher degree of overlap and, + thus, achieves better ellipse-based shape approximation. + 10.1016/j.imavis.2019.09.001 diff --git a/tools/rfove/tests.xml b/tools/rfove/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/rfove/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/scale_image/creators.xml b/tools/scale_image/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/scale_image/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/scale_image/scale_image.py b/tools/scale_image/scale_image.py index 123e1311..47957f5f 100644 --- a/tools/scale_image/scale_image.py +++ b/tools/scale_image/scale_image.py @@ -1,38 +1,50 @@ import argparse import sys -import scipy.misc +import numpy as np import skimage.io import skimage.transform +import skimage.util from PIL import Image -def scale_image(input_file, output_file, scale, order=1): +def scale_image(input_file, output_file, scale, order, antialias): Image.MAX_IMAGE_PIXELS = 50000 * 50000 - img_in = skimage.io.imread(input_file) - if order == 0: - interp = 'nearest' - elif order == 1: - interp = 'bilinear' - elif order == 2: - interp = 'bicubic' + im = skimage.io.imread(input_file) + + # Parse `--scale` argument if ',' in scale: - scale = scale[1:-1].split(',') - scale = [int(i) for i in scale] - elif '.' in scale: - scale = float(scale) + scale = [float(s.strip()) for s in scale.split(',')] + assert len(scale) <= im.ndim, f'Image has {im.ndim} axes, but scale factors were given for {len(scale)} axes.' + scale = scale + [1] * (im.ndim - len(scale)) + else: - scale = int(scale) - res = scipy.misc.imresize(img_in, scale, interp=interp) + scale = float(scale) + + # For images with 3 or more axes, the last axis is assumed to correspond to channels + if im.ndim >= 3: + scale = [scale] * (im.ndim - 1) + [1] + + # Do the scaling + res = skimage.transform.rescale(im, scale, order, anti_aliasing=antialias, preserve_range=True) + + # Preserve the `dtype` so that both brightness and range of values is preserved + if res.dtype != im.dtype: + if np.issubdtype(im.dtype, np.integer): + res = res.round() + res = res.astype(im.dtype) + + # Save result skimage.io.imsave(output_file, res) if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('input_file', type=argparse.FileType('r'), default=sys.stdin, help='input file') - parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin, help='out file (PNG)') - parser.add_argument('scale', type=str, help='fraction scaling factor(float), percentage scaling factor(int), output size(tuple(height,width))') # integer option not implemented in galaxy wrapper - parser.add_argument('order', type=int, default=1, help='interpolation method') + parser.add_argument('input_file', type=argparse.FileType('r'), default=sys.stdin) + parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin) + parser.add_argument('--scale', type=str, required=True) + parser.add_argument('--order', type=int, required=True) + parser.add_argument('--antialias', default=False, action='store_true') args = parser.parse_args() - scale_image(args.input_file.name, args.out_file.name, args.scale, args.order) + scale_image(args.input_file.name, args.out_file.name, args.scale, args.order, args.antialias) diff --git a/tools/scale_image/scale_image.xml b/tools/scale_image/scale_image.xml index dcc11523..d560f3e0 100644 --- a/tools/scale_image/scale_image.xml +++ b/tools/scale_image/scale_image.xml @@ -1,5 +1,14 @@ - + with scikit-image + + creators.xml + tests.xml + 0.18.3 + 0 + + + + operation_3443 @@ -8,64 +17,84 @@ scikit-image - pillow - scikit-image - numpy - scipy - tifffile + scikit-image + pillow + numpy + tifffile - - - + python '$__tool_directory__/scale_image.py' '$input' + + ./output.${input.ext} + + --scale '$scale' + --order $order + $antialias + + && mv ./output.${input.ext} ./output + + ]]> - - - - - - - - - - - - - - + + + - + + - - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - **What it does** - This tool scales an image using the scaling factor. + **Scales an image using one or more scaling factors.** + + The image is rescaled uniformly along all axes, or anistropically if multiple scale factors are given. + + This operation preserves both the brightness of the image, and the range of values. + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/scale_image/test-data/anisotropic.png b/tools/scale_image/test-data/anisotropic.png new file mode 100644 index 00000000..6d9e4a8a Binary files /dev/null and b/tools/scale_image/test-data/anisotropic.png differ diff --git a/tools/scale_image/test-data/input1_binary_rgb.png b/tools/scale_image/test-data/input1_binary_rgb.png new file mode 100644 index 00000000..221ab286 Binary files /dev/null and b/tools/scale_image/test-data/input1_binary_rgb.png differ diff --git a/tools/scale_image/test-data/input2_normalized.tiff b/tools/scale_image/test-data/input2_normalized.tiff new file mode 100644 index 00000000..f33d8ba9 Binary files /dev/null and b/tools/scale_image/test-data/input2_normalized.tiff differ diff --git a/tools/scale_image/test-data/input3_not_normalized.tiff b/tools/scale_image/test-data/input3_not_normalized.tiff new file mode 100644 index 00000000..33cf72c7 Binary files /dev/null and b/tools/scale_image/test-data/input3_not_normalized.tiff differ diff --git a/tools/scale_image/test-data/normalized.tiff b/tools/scale_image/test-data/normalized.tiff new file mode 100644 index 00000000..21f0e11b Binary files /dev/null and b/tools/scale_image/test-data/normalized.tiff differ diff --git a/tools/scale_image/test-data/not_normalized.tiff b/tools/scale_image/test-data/not_normalized.tiff new file mode 100644 index 00000000..a63a6656 Binary files /dev/null and b/tools/scale_image/test-data/not_normalized.tiff differ diff --git a/tools/scale_image/test-data/out.png b/tools/scale_image/test-data/out.png deleted file mode 100644 index 00bef48f..00000000 Binary files a/tools/scale_image/test-data/out.png and /dev/null differ diff --git a/tools/scale_image/test-data/out2.png b/tools/scale_image/test-data/out2.png deleted file mode 100644 index 00bef48f..00000000 Binary files a/tools/scale_image/test-data/out2.png and /dev/null differ diff --git a/tools/scale_image/test-data/sample1.png b/tools/scale_image/test-data/sample1.png deleted file mode 100644 index 2283a3b6..00000000 Binary files a/tools/scale_image/test-data/sample1.png and /dev/null differ diff --git a/tools/scale_image/test-data/uniform.png b/tools/scale_image/test-data/uniform.png new file mode 100644 index 00000000..5667687a Binary files /dev/null and b/tools/scale_image/test-data/uniform.png differ diff --git a/tools/scale_image/test-data/uniform_binary.png b/tools/scale_image/test-data/uniform_binary.png new file mode 100644 index 00000000..6b215423 Binary files /dev/null and b/tools/scale_image/test-data/uniform_binary.png differ diff --git a/tools/scale_image/tests.xml b/tools/scale_image/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/scale_image/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/segmetrics/creators.xml b/tools/segmetrics/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/segmetrics/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/segmetrics/segmetrics.xml b/tools/segmetrics/segmetrics.xml index 89981fba..90a0d49d 100644 --- a/tools/segmetrics/segmetrics.xml +++ b/tools/segmetrics/segmetrics.xml @@ -1,9 +1,13 @@ with SegMetrics + creators.xml 1.4 - 4 + 5 + + + operation_3443 @@ -50,8 +54,8 @@ - - + + @@ -315,11 +319,13 @@ - This tool permits the computation of image segmentation and object detection performance measures for 2-D image data. + + **Computates image segmentation and object detection performance measures for 2-D image data.** You can either use a pair of individual input images (a segmented and a ground truth image), or a pair of ZIP archives which contain the segmented and the correspondiong ground truth images. When using a pair of individual images, remember to turn off the "Unzip" option. When using ZIP archives, turn it on instead and make sure that the directory structure is the same for the segmented and the ground truth images. Correspondences are estbalished based on the file names, which should be *identical* for a pair of corresponding images. If all objects within your segmented images are *uniquely* labeled, you can turn on the "Segmentation is uniquely labeled" switch to speed up the computations. Leave it off otherwise, or if you are unsure. The same accounts for the "Ground truth is uniquely labeled" switch and your ground turth image data. + 10.1093/bioinformatics/btu080 diff --git a/tools/slice_image/creators.xml b/tools/slice_image/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/slice_image/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/slice_image/slice_image.py b/tools/slice_image/slice_image.py index 30f3582a..a6f552d0 100644 --- a/tools/slice_image/slice_image.py +++ b/tools/slice_image/slice_image.py @@ -9,12 +9,9 @@ import skimage.util -def slice_image(input_file, out_folder, label=None, label_out_folder=None, window_size=64, - stride=1, bg_thresh=1, limit_slices=False, n_thresh=5000, seed=None): - # TODO NOT Implemented:process labels - # --> label and label_out_folder useless so far +def slice_image(input_file, out_folder, window_size=64, stride=1, bg_thresh=1, limit_slices=False, n_thresh=5000, seed=None): - # primarily for testing purposes: + # Primarily for testing purposes if seed is not None: random.seed(seed) @@ -40,7 +37,7 @@ def slice_image(input_file, out_folder, label=None, label_out_folder=None, windo sum_image = skimage.util.img_as_uint(sum_image) g = skimage.feature.greycomatrix(sum_image, [1, 2], [0, np.pi / 2], nnormed=True, symmetric=True) hom = np.var(skimage.feature.greycoprops(g, prop='homogeneity')) - if hom > bg_thresh: # 0.0005 + if hom > bg_thresh: continue if limit_slices: @@ -54,19 +51,22 @@ def slice_image(input_file, out_folder, label=None, label_out_folder=None, windo if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('input_file', type=argparse.FileType('r'), help='input file') - parser.add_argument('out_folder', help='out folder') - parser.add_argument('--label', dest='label_file', default=None, help='auxiliary label file to split in the same way') - parser.add_argument('--label_out_folder', dest='label_out_folder', default=None, help='label out folder') - parser.add_argument('--stride', dest='stride', type=int, default=1, help='applied stride') - parser.add_argument('--window_size', dest='window_size', type=int, default=64, help='size of resulting patches') - parser.add_argument('--bg_thresh', dest='bg_thresh', type=float, default=0, help='skip patches without information using a treshold') - parser.add_argument('--limit_slices', dest='limit_slices', type=bool, default=False, help='limit amount of slices') - parser.add_argument('--n_thresh', dest='n_thresh', type=int, default=5000, help='amount of slices') - parser.add_argument('--seed', dest='seed', type=int, default=None, help='seed for random choice of limited slices') + parser.add_argument('input_file', type=argparse.FileType('r'), help='Input file') + parser.add_argument('out_folder', help='Output directory') + parser.add_argument('--stride', dest='stride', type=int, default=1, help='Applied stride') + parser.add_argument('--window_size', dest='window_size', type=int, default=64, help='Size of resulting patches') + parser.add_argument('--bg_thresh', dest='bg_thresh', type=float, default=0, help='Skip background patches without information using a treshold') + parser.add_argument('--n_thresh', dest='n_thresh', type=int, default=5000, help='Maximum number of slices to retain') + parser.add_argument('--seed', dest='seed', type=int, default=None, help='Seed for random choice of slices') args = parser.parse_args() - slice_image(args.input_file.name, args.out_folder, - label=args.label_file, label_out_folder=args.label_out_folder, - stride=args.stride, window_size=args.window_size, bg_thresh=args.bg_thresh, - limit_slices=args.limit_slices, n_thresh=args.n_thresh, seed=args.seed) + slice_image( + args.input_file.name, + args.out_folder, + stride=args.stride, + window_size=args.window_size, + bg_thresh=args.bg_thresh, + limit_slices=args.n_thresh > 0, + n_thresh=args.n_thresh, + seed=args.seed, + ) diff --git a/tools/slice_image/slice_image.xml b/tools/slice_image/slice_image.xml index 1352a122..7c371bbc 100644 --- a/tools/slice_image/slice_image.xml +++ b/tools/slice_image/slice_image.xml @@ -1,5 +1,12 @@ - + + + creators.xml + tests.xml + + + + operation_3443 @@ -12,34 +19,35 @@ scipy tifffile - - 0: + --n_thresh '$n_thresh' #end if - #if $control_rng: - --seed $seed + + #if len('$seed') > 0: + --seed '$seed' #end if + && ls -l ./out - ]]> - + + ]]> - - - - - - - - - + + + + + + @@ -47,25 +55,38 @@ + - - - - - - - + + + + + + - **What it does** - Slices image into multiple smaller patches. + **Slices an image into multiple smaller, square-shaped patches.** + + For overlapping patches, set the stride to a value smaller than the patch size. + For non-overlapping patches, set the stride to a value identical to the patch size (or larger). + If the stride is set to a value larger than the patch size, parts of the original image will be skipped. + + Optionally, patches entirely corresponding to image background are discarded. + To decide whether a patch corresponds to image background, the `homogeneity`_ of its `gray-level co-occurrence matrix`_ is considered. + + .. _homogeneity: https://scikit-image.org/docs/stable/api/skimage.feature.html#skimage.feature.graycoprops + .. _gray-level co-occurrence matrix: https://scikit-image.org/docs/stable/api/skimage.feature.html#skimage.feature.graycomatrix + + In addition, the number of the remaining patches can be reduced by specifying a maximum number of patches to retain. + Those will be selected randomly. + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/slice_image/tests.xml b/tools/slice_image/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/slice_image/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/split_labelmap/creators.xml b/tools/split_labelmap/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/split_labelmap/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/split_labelmap/split_labelmap.xml b/tools/split_labelmap/split_labelmap.xml index 34a7ba9f..0df49b1f 100644 --- a/tools/split_labelmap/split_labelmap.xml +++ b/tools/split_labelmap/split_labelmap.xml @@ -1,5 +1,12 @@ + + creators.xml + tests.xml + + + + operation_3443 @@ -25,14 +32,16 @@ - + - **What it does** - Takes a labeled image and outputs a similar file where the labeled parts - of the image that touch (or overlap) are separated by at least 1 pixel (at most 2). + **Splits label map using morphological operators.** + + Takes a labeled image and outputs a similar file where the labeled parts + of the image that touch (or overlap) are separated by at least 1 pixel (at most 2 pixels). + 10.1016/j.jbiotec.2017.07.019 diff --git a/tools/split_labelmap/tests.xml b/tools/split_labelmap/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/split_labelmap/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/superdsm/creators.xml b/tools/superdsm/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/superdsm/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/superdsm/superdsm.xml b/tools/superdsm/superdsm.xml index 814899ef..2bc38080 100644 --- a/tools/superdsm/superdsm.xml +++ b/tools/superdsm/superdsm.xml @@ -1,9 +1,14 @@ with SuperDSM + creators.xml + tests.xml 0.2.0 0 + + + operation_3443 @@ -13,7 +18,7 @@ - superdsm + superdsm + + + @@ -170,9 +185,13 @@ - This tool permits the segmentation of cell nuclei in 2-D fluorescence microscopy images. + + **Performs segmentation of 2-D fluorescence microscopy images using deformable shape models and superadditivity.** + + SuperDSM is a globally optimal method for cell nuclei segmentation using deformable shape models and their inherent law of superadditivity. You can either use an individual input image (PNG, TIF) or a collection of such images. + diff --git a/tools/superdsm/tests.xml b/tools/superdsm/tests.xml new file mode 120000 index 00000000..e20d710a --- /dev/null +++ b/tools/superdsm/tests.xml @@ -0,0 +1 @@ +../../macros/tests.xml \ No newline at end of file diff --git a/tools/voronoi_tessellation/creators.xml b/tools/voronoi_tessellation/creators.xml new file mode 120000 index 00000000..5d2b71e0 --- /dev/null +++ b/tools/voronoi_tessellation/creators.xml @@ -0,0 +1 @@ +../../macros/creators.xml \ No newline at end of file diff --git a/tools/voronoi_tessellation/test-data/input1_result.tiff b/tools/voronoi_tessellation/test-data/input1_result.tiff deleted file mode 100644 index 7ffebe26..00000000 Binary files a/tools/voronoi_tessellation/test-data/input1_result.tiff and /dev/null differ diff --git a/tools/voronoi_tessellation/voronoi_tessellation.xml b/tools/voronoi_tessellation/voronoi_tessellation.xml index 8ee7610a..75d7a536 100644 --- a/tools/voronoi_tessellation/voronoi_tessellation.xml +++ b/tools/voronoi_tessellation/voronoi_tessellation.xml @@ -1,8 +1,9 @@ with scikit-image + creators.xml 0.22.0 - 0 + 1 operation_3443 @@ -12,9 +13,9 @@ scikit-image + scikit-image numpy scipy - scikit-image - + @@ -37,12 +38,23 @@ - + + + + + + + + + + + - This tool computes Voronoi tessellations for labeled images. + **Computes Voronoi tessellations for labeled images.** + Voronoi tessellations are also known as Vornoi diagrams, or Dirichlet tessellations. Zero labels are treated as image background. diff --git a/util/.gitignore b/util/.gitignore new file mode 100644 index 00000000..6f71c9bc --- /dev/null +++ b/util/.gitignore @@ -0,0 +1 @@ +.*.venv diff --git a/util/README.md b/util/README.md new file mode 100644 index 00000000..a1677148 --- /dev/null +++ b/util/README.md @@ -0,0 +1,31 @@ +# Utility scripts + +## Use case: An input TIFF file is too large + +Assuming that the TIFF file is an RGB file: + +```bash +../../util/shrink_tiff.sh test-data/input2_float.tiff --channel_axis 2 +``` + +The script preserves the brightness and range of values of the image. + +## Use case: An output TIFF file is too large + +Assuming that the TIFF file is an RGB file: + +```bash +../../util/shrink_tiff.sh test-data/res_preserve_values.tiff --channel_axis 2 +``` + +This shrinks the file, but the *input files* also need to be shrinked accordingly. +The output of the above command tells the exact scaling factor that was used. +Denote this factor, and then run: + +```bash +../../util/scale_image.sh test-data/input2_float.tiff --channel_axis 2 --scale SCALE +``` + +where you replace `SCALE` by the denoted factor. This also works for PNG files. + +The script preserves the brightness and range of values of the image. diff --git a/util/scale_image.py b/util/scale_image.py new file mode 100644 index 00000000..2603d5ad --- /dev/null +++ b/util/scale_image.py @@ -0,0 +1,40 @@ +import argparse + +import numpy as np +import skimage.transform +import skimage.io + + +def scale_image(filepath, **kwargs): + im = skimage.io.imread(filepath) + res = skimage.transform.rescale(im, preserve_range=True, **kwargs) + + # Preserve the `dtype` so that both brightness and range of values is preserved + if res.dtype != im.dtype: + if np.issubdtype(im.dtype, np.integer): + res = res.round() + res = res.astype(im.dtype) + + skimage.io.imsave(filepath, res) + return res + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + parser.add_argument('filepath', type=str, help='Image filepath') + parser.add_argument('--scale', type=float, help='Scaling factor', required=True) + parser.add_argument('--anti_aliasing', default=False, action='store_true', help='Use anti-aliasing') + parser.add_argument('--order', type=int, default=0, help='Order of interpolation (0: nearest-neighbor, 1: bi-linear)') + parser.add_argument('--channel_axis', type=int, default=None, help='Given axis will not be rescaled') + args = parser.parse_args() + + res = scale_image( + filepath=args.filepath, + scale=args.scale, + anti_aliasing=args.anti_aliasing, + order=args.order, + channel_axis=args.channel_axis, + ) + + print(f'File {args.filepath} has been scaled to a resolution of {str(res.shape)} pixels.') diff --git a/util/scale_image.sh b/util/scale_image.sh new file mode 100755 index 00000000..d673d1c2 --- /dev/null +++ b/util/scale_image.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +cwd=$( pwd ) +cd "$script_path" + +if [ ! -d ".scale_image.venv" ] +then + python -m venv ".scale_image.venv" + source ".scale_image.venv/bin/activate" + pip install "scikit-image>=0.19" "tifffile" "pillow" +else + source ".scale_image.venv/bin/activate" +fi + +cd "$cwd" +python "$script_path/scale_image.py" $* diff --git a/util/shrink_tiff.py b/util/shrink_tiff.py new file mode 100644 index 00000000..13b2c50f --- /dev/null +++ b/util/shrink_tiff.py @@ -0,0 +1,85 @@ +import argparse +import itertools +import math +import os +import tempfile +import shutil + +import humanize +import skimage.transform +import tifffile + + +DEFAULT_MAX_SIZE = 1024 ** 2 + + +def shrink_tiff(filepath, anti_aliasing, order, channel_axis, max_size=DEFAULT_MAX_SIZE, dry=True): + im_full = tifffile.imread(filepath) + + for step in itertools.count(1): + scale = math.sqrt(1 / step) + im = skimage.transform.rescale( + im_full, + scale=scale, + anti_aliasing=anti_aliasing, + order=order, + preserve_range=True, + channel_axis=channel_axis, + ) + + with tempfile.NamedTemporaryFile(suffix='.tiff', mode='wb') as fp: + tifffile.imwrite(fp.name, im) + byte_size = os.path.getsize(fp.name) + + if byte_size <= max_size: + if not dry: + shutil.copy(fp.name, filepath) + return im, scale, byte_size + + +if __name__ == '__main__': + + exec_name = 'shrink_tiff.sh' + long_help = f""" + + Example for single-channel binary images and label maps: + + {exec_name} filepath.tiff + + Example for multi-channel binary images: + + {exec_name} filepath.tiff --channel_axis 2 + + Example for single-channel intensity images: + + {exec_name} filepath.tiff --anti_aliasing --order 1 + + """ + + parser = argparse.ArgumentParser() + parser.add_argument('filepath', type=str, help='TIFF filepath') + parser.add_argument('--max_size', type=int, default=DEFAULT_MAX_SIZE, help='Maximum size in bytes') + parser.add_argument('--anti_aliasing', default=False, action='store_true', help='Use anti-aliasing') + parser.add_argument('--order', type=int, default=0, help='Order of interpolation (0: nearest-neighbor, 1: bi-linear)') + parser.add_argument('--channel_axis', type=int, default=None, help='Given axis will not be rescaled') + parser.add_argument('--run', default=False, action='store_true', help='Update the TIFF file (default is dry run)') + args = parser.parse_args() + + if not args.run: + print(long_help) + + result, scale, result_size = shrink_tiff( + filepath=args.filepath, + max_size=args.max_size, + anti_aliasing=args.anti_aliasing, + order=args.order, + channel_axis=args.channel_axis, + dry=not args.run, + ) + + result_size_str = humanize.naturalsize(result_size, binary=True) + if args.run: + print(f'File {args.filepath} has been shrinked to {result_size_str} and a resolution of {str(result.shape)} pixels by a factor of {scale}.') + else: + print(f'File {args.filepath} will be shrinked to {result_size_str} and a resolution of {str(result.shape)} pixels by a factor of {scale}.') + print('\nUse the switch "--run" to update the file.') diff --git a/util/shrink_tiff.sh b/util/shrink_tiff.sh new file mode 100755 index 00000000..ad4ff4ab --- /dev/null +++ b/util/shrink_tiff.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +cwd=$( pwd ) +cd "$script_path" + +if [ ! -d ".shrink_tiff.venv" ] +then + python -m venv ".shrink_tiff.venv" + source ".shrink_tiff.venv/bin/activate" + pip install "scikit-image>=0.19" "humanize" "tifffile" +else + source ".shrink_tiff.venv/bin/activate" +fi + +cd "$cwd" +python "$script_path/shrink_tiff.py" $*