changeset 2:227e8928af6e draft default tip

planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/split_image/ commit 5e452f10eb88f0fa8a420eec66c6c97e3060e433
author imgteam
date Fri, 12 Dec 2025 21:02:25 +0000
parents 4b7940d0c051
children
files creators.xml split.py split_image.xml test-data/multiseries.ome.tiff test-data/multiseries_series1.tiff test-data/multiseries_series6.tiff test-data/rgb1.tiff
diffstat 7 files changed, 220 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/creators.xml	Sat Apr 12 15:05:17 2025 +0000
+++ b/creators.xml	Fri Dec 12 21:02:25 2025 +0000
@@ -5,6 +5,11 @@
         <yield />
     </xml>
 
+    <xml name="creators/kostrykin">
+        <person givenName="Leonid" familyName="Kostrykin"/>
+        <yield/>
+    </xml>
+
     <xml name="creators/rmassei">
         <person givenName="Riccardo" familyName="Massei"/>
         <yield/>
@@ -24,5 +29,15 @@
         <person givenName="Till" familyName="Korten"/>
         <yield/>
     </xml>
-    
+
+    <xml name="creators/pavanvidem">
+        <person givenName="Pavan" familyName="Videm"/>
+        <yield/>
+    </xml>
+
+    <xml name="creators/tuncK">
+        <person givenName="Tunc" familyName="Kayikcioglu"/>
+        <yield/>
+    </xml>
+
 </macros>
--- a/split.py	Sat Apr 12 15:05:17 2025 +0000
+++ b/split.py	Fri Dec 12 21:02:25 2025 +0000
@@ -1,54 +1,122 @@
 import argparse
 import math
+import os
+import pathlib
+import shutil
 
 import giatools
-import giatools.util
 import numpy as np
 import tifffile
 
 
-parser = argparse.ArgumentParser()
-parser.add_argument('input', type=str)
-parser.add_argument('axis', type=str)
-parser.add_argument('output', type=str)
-parser.add_argument('--squeeze', action='store_true', default=False)
-args = parser.parse_args()
+class OutputWriter:
+
+    def __init__(self, dir_path: pathlib.Path, num_images: int, squeeze: bool, verbose: bool):
+        print(f'Writing {num_images} image(s)')
+        decimals = math.ceil(math.log10(1 + num_images))
+        self.output_filepath_pattern = str(dir_path / f'%0{decimals}d.tiff')
+        self.last_idx = 0
+        self.squeeze = squeeze
+        self.verbose = verbose
+
+    def write(self, img: giatools.Image):
+        self.last_idx += 1
+        if self.squeeze:
+            img = img.squeeze()
+        if self.last_idx == 1 or self.verbose:
+            prefix = f'Output {self.last_idx}' if self.verbose else 'Output'
+            print(f'{prefix} axes:', img.axes)
+            print(f'{prefix} shape:', img.data.shape)
+        img.write(self.output_filepath_pattern % self.last_idx)
+
 
-# Validate and normalize input parameters
-assert len(args.axis) == 1, 'Axis input must be a single character.'
-axis = args.axis.replace('S', 'C')
+if __name__ == '__main__':
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('input', type=pathlib.Path)
+    parser.add_argument('axis', type=str, choices=list(giatools.default_normalized_axes) + ['S', ''])
+    parser.add_argument('output', type=pathlib.Path)
+    parser.add_argument('--squeeze', action='store_true', default=False)
+    args = parser.parse_args()
 
-# Read input image with normalized axes
-img_in = giatools.Image.read(args.input)
+    # If splitting a file that contains multiple images...
+    if args.axis == '':
 
-# Determine the axis to split along
-axis_pos = img_in.axes.index(axis)
+        # Peek the number of series in the input file (if it is a TIFF)
+        try:
+            with tifffile.TiffFile(args.input) as tiff:
+                num_tiff_series = len(tiff.series)
+                print(f'Found TIFF with {num_tiff_series} series')
+        except tifffile.TiffFileError:
+            num_tiff_series = 0  # not a TIFF file
+            print('Not a TIFF file')
 
-# Perform the splitting
-arr = np.moveaxis(img_in.data, axis_pos, 0)
-decimals = math.ceil(math.log10(1 + arr.shape[0]))
-output_filename_pattern = f'{args.output}/%0{decimals}d.tiff'
-for img_idx, img in enumerate(arr):
-    img = np.moveaxis(img[None], 0, axis_pos)
+        # If the file is a multi-series TIFF, extract the individual series
+        # (for consistency, also accept only a single series if squeezing is requested)
+        if num_tiff_series >= 2 or (num_tiff_series == 1 and args.squeeze):
+            output = OutputWriter(
+                dir_path=args.output,
+                num_images=num_tiff_series,
+                squeeze=args.squeeze,
+                verbose=True,
+            )
+            for series in range(num_tiff_series):
+                img = giatools.Image.read(args.input, series=series)
+                output.write(
+                    img.squeeze_like(img.original_axes),
+                )
+
+        # Otherwise, there is nothing to be split (or squeeze)
+        # (the input is either a single-series TIFF or not a TIFF at all)
+        elif num_tiff_series == 1:  # input is a single-series TIFF (output = input)
+            try:
+                os.symlink(args.input, args.output / '1.tiff')
+            except OSError:
+                shutil.copyfile(args.input, args.output / '1.tiff')
+        else:  # input is not a TIFF, conversion needed
+            img = giatools.Image.read(args.input)
+            OutputWriter(
+                dir_path=args.output,
+                num_images=1,
+                squeeze=args.squeeze,
+                verbose=False,
+            ).write(
+                img.squeeze_like(img.original_axes),
+            )
 
-    # Construct the output image, remove axes added by normalization
-    img_out = giatools.Image(
-        data=img,
-        axes=img_in.axes,
-    ).squeeze_like(
-        img_in.original_axes,
-    )
+    # If splitting along an image axes...
+    else:
+
+        # Validate and normalize input parameters
+        axis = args.axis.replace('S', 'C')
+
+        # Read input image with normalized axes
+        img_in = giatools.Image.read(args.input)
+        print('Input image axes:', img_in.original_axes)
+        print('Input image shape:', img_in.squeeze_like(img_in.original_axes).data.shape)
+
+        # Determine the axis to split along
+        axis_pos = img_in.axes.index(axis)
 
-    # Optionally, squeeze the image
-    if args.squeeze:
-        s = [
-            axis_pos for axis_pos in range(len(img_out.axes))
-            if img_out.data.shape[axis_pos] == 1 and img_out.axes[axis_pos] not in 'YX'
-        ]
-        img_out = img_out.squeeze_like(
-            giatools.util.str_without_positions(img_out.axes, s),
+        # Perform the splitting
+        arr = np.moveaxis(img_in.data, axis_pos, 0)
+        output = OutputWriter(
+            dir_path=args.output,
+            num_images=arr.shape[0],
+            squeeze=args.squeeze,
+            verbose=False,
         )
+        for img_idx, img in enumerate(arr):
+            img = np.moveaxis(img[None], 0, axis_pos)
 
-    # Save the result
-    filename = output_filename_pattern % (img_idx + 1)
-    tifffile.imwrite(filename, img_out.data, metadata=dict(axes=img_out.axes))
+            # Construct the output image, remove axes added by normalization
+            img_out = giatools.Image(
+                data=img,
+                axes=img_in.axes,
+                metadata=img_in.metadata,
+            ).squeeze_like(
+                img_in.original_axes,
+            )
+
+            # Save the result (write stdout during first iteration)
+            output.write(img_out)
--- a/split_image.xml	Sat Apr 12 15:05:17 2025 +0000
+++ b/split_image.xml	Fri Dec 12 21:02:25 2025 +0000
@@ -3,19 +3,23 @@
     <macros>
         <import>creators.xml</import>
         <import>tests.xml</import>
-        <token name="@TOOL_VERSION@">2.2.3</token>
-        <token name="@VERSION_SUFFIX@">1</token>
+        <token name="@TOOL_VERSION@">2.3.5</token>
+        <token name="@VERSION_SUFFIX@">0</token>
     </macros>
     <creator>
         <expand macro="creators/bmcv"/>
+        <expand macro="creators/kostrykin"/>
     </creator>
     <edam_operations>
         <edam_operation>operation_3443</edam_operation>
     </edam_operations>
+    <xrefs>
+        <xref type="bio.tools">galaxy_image_analysis</xref>
+    </xrefs>
     <requirements>
         <requirement type="package" version="@TOOL_VERSION@">numpy</requirement>
-        <requirement type="package" version="0.4.0">giatools</requirement>
-        <requirement type="package" version="2024.7.24">tifffile</requirement>
+        <requirement type="package" version="0.5.2">giatools</requirement>
+        <requirement type="package" version="2025.10.16">tifffile</requirement>
     </requirements>
     <command detect_errors="aggressive"><![CDATA[
 
@@ -37,8 +41,10 @@
             <option value="C" selected="true">C-axis (split the channels of an image or image sequence)</option>
             <option value="S">S-axis (split the samples of an image or image sequence)</option>
             <option value="Q">Q-axis (other or unknown axis)</option>
+            <option value="">Split dataset that contains multiple images (e.g., multi-series TIFF)</option>
         </param>
-        <param name="squeeze" type="boolean" checked="false" truevalue="--squeeze" falsevalue="" label="Squeeze result imags" help="Only axes with more than one element will be retained in the result images. Does not apply for X and Y axes." />
+        <param name="squeeze" type="boolean" checked="false" truevalue="--squeeze" falsevalue="" label="Squeeze result images"
+               help="Only non-singleton axes (axes with more than one element) will be retained in the result images. This does not apply for the X and Y axes which always are retained." />
     </inputs>
     <outputs>
         <collection type="list" name="output" label="Split ${on_string} along ${axis} axis">
@@ -57,6 +63,27 @@
                 <expand macro="tests/intensity_image_diff/element" name="2.tiff" value="rgb1_g.tiff" ftype="tiff"/>
                 <expand macro="tests/intensity_image_diff/element" name="3.tiff" value="rgb1_b.tiff" ftype="tiff"/>
             </output_collection>
+            <assert_stdout>
+                <has_line line="Input image axes: YXC"/>
+                <has_line line="Input image shape: (32, 32, 3)"/>
+                <has_line line="Writing 3 image(s)"/>
+                <has_line line="Output axes: YXC"/>
+                <has_line line="Output shape: (32, 32, 1)"/>
+            </assert_stdout>
+        </test>
+        <test>
+            <param name="input" value="rgb1.png" />
+            <param name="axis" value="" />
+            <param name="squeeze" value="false" />
+            <output_collection name="output" type="list" count="1">
+                <expand macro="tests/intensity_image_diff/element" name="1.tiff" value="rgb1.tiff" ftype="tiff"/>
+            </output_collection>
+            <assert_stdout>
+                <has_line line="Not a TIFF file"/>
+                <has_line line="Writing 1 image(s)"/>
+                <has_line line="Output axes: YXC"/>
+                <has_line line="Output shape: (32, 32, 3)"/>
+            </assert_stdout>
         </test>
 
         <!-- TIFF tests -->
@@ -68,6 +95,13 @@
                 <expand macro="tests/intensity_image_diff/element" name="01.tiff" value="zcyx_slice01.tiff" ftype="tiff"/>
                 <expand macro="tests/intensity_image_diff/element" name="25.tiff" value="zcyx_slice25.tiff" ftype="tiff"/>
             </output_collection>
+            <assert_stdout>
+                <has_line line="Input image axes: ZCYX"/>
+                <has_line line="Input image shape: (25, 2, 50, 50)"/>
+                <has_line line="Writing 25 image(s)"/>
+                <has_line line="Output axes: ZCYX"/>
+                <has_line line="Output shape: (1, 2, 50, 50)"/>
+            </assert_stdout>
         </test>
         <test>
             <param name="input" value="qyx.tiff" />
@@ -77,6 +111,51 @@
                 <expand macro="tests/intensity_image_diff/element" name="1.tiff" value="qyx_q1.tiff" ftype="tiff"/>
                 <expand macro="tests/intensity_image_diff/element" name="2.tiff" value="qyx_q2.tiff" ftype="tiff"/>
             </output_collection>
+            <assert_stdout>
+                <has_line line="Input image axes: QYX"/>
+                <has_line line="Input image shape: (2, 256, 256)"/>
+                <has_line line="Writing 2 image(s)"/>
+                <has_line line="Output axes: QYX"/>
+                <has_line line="Output shape: (1, 256, 256)"/>
+            </assert_stdout>
+        </test>
+        <test>
+            <param name="input" value="qyx.tiff" />
+            <param name="axis" value="" />
+            <param name="squeeze" value="false" />
+            <output_collection name="output" type="list" count="1">
+                <expand macro="tests/intensity_image_diff/element" name="1.tiff" value="qyx.tiff" ftype="tiff"/>
+            </output_collection>
+            <assert_stdout>
+                <has_line line="Found TIFF with 1 series"/>
+            </assert_stdout>
+        </test>
+
+        <!-- Test splitting multi-series TIFF -->
+        <test>
+            <param name="input" value="multiseries.ome.tiff" />
+            <param name="axis" value="" />
+            <param name="squeeze" value="false" />
+            <output_collection name="output" type="list" count="6">
+                <expand macro="tests/intensity_image_diff/element" name="1.tiff" value="multiseries_series1.tiff" ftype="tiff"/>
+                <expand macro="tests/intensity_image_diff/element" name="6.tiff" value="multiseries_series6.tiff" ftype="tiff"/>
+            </output_collection>
+            <assert_stdout>
+                <has_line line="Found TIFF with 6 series"/>
+                <has_line line="Writing 6 image(s)"/>
+                <has_line line="Output 1 axes: CYX"/>
+                <has_line line="Output 1 shape: (4, 5, 5)"/>
+                <has_line line="Output 2 axes: CYX"/>
+                <has_line line="Output 2 shape: (4, 5, 5)"/>
+                <has_line line="Output 3 axes: CYX"/>
+                <has_line line="Output 3 shape: (4, 5, 5)"/>
+                <has_line line="Output 4 axes: CYX"/>
+                <has_line line="Output 4 shape: (4, 5, 5)"/>
+                <has_line line="Output 5 axes: CYX"/>
+                <has_line line="Output 5 shape: (4, 5, 5)"/>
+                <has_line line="Output 6 axes: CYX"/>
+                <has_line line="Output 6 shape: (4, 5, 5)"/>
+            </assert_stdout>
         </test>
 
         <!-- Test squeezing -->
@@ -89,6 +168,13 @@
                 <expand macro="tests/intensity_image_diff/element" name="2.tiff" value="rgb1_squeezed_g.tiff" ftype="tiff"/>
                 <expand macro="tests/intensity_image_diff/element" name="3.tiff" value="rgb1_squeezed_b.tiff" ftype="tiff"/>
             </output_collection>
+            <assert_stdout>
+                <has_line line="Input image axes: YXC"/>
+                <has_line line="Input image shape: (32, 32, 3)"/>
+                <has_line line="Writing 3 image(s)"/>
+                <has_line line="Output axes: YX"/>
+                <has_line line="Output shape: (32, 32)"/>
+            </assert_stdout>
         </test>
 
         <!-- Test with missing axes -->
@@ -99,6 +185,13 @@
             <output_collection name="output" type="list" count="1">
                 <expand macro="tests/intensity_image_diff/element" name="1.tiff" value="rgb1_split_z.tiff" ftype="tiff"/>
             </output_collection>
+            <assert_stdout>
+                <has_line line="Input image axes: YXC"/>
+                <has_line line="Input image shape: (32, 32, 3)"/>
+                <has_line line="Writing 1 image(s)"/>
+                <has_line line="Output axes: YXC"/>
+                <has_line line="Output shape: (32, 32, 3)"/>
+            </assert_stdout>
         </test>
 
     </tests>
Binary file test-data/multiseries.ome.tiff has changed
Binary file test-data/multiseries_series1.tiff has changed
Binary file test-data/multiseries_series6.tiff has changed
Binary file test-data/rgb1.tiff has changed