Repository 'points2binaryimage'
hg clone https://toolshed.g2.bx.psu.edu/repos/imgteam/points2binaryimage

Changeset 4:4c0f16fb9f8d (2024-09-25)
Previous changeset 3:a80f92ec6f89 (2024-06-26) Next changeset 5:5411384e5e1d (2024-09-27)
Commit message:
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/points2binaryimage/ commit a3c77a79db469c9ad18b666f2c64d6e8f573945f
modified:
creators.xml
points2binaryimage.py
points2binaryimage.xml
added:
test-data/input1.tsv
test-data/input2.tsv
test-data/output1.original.tif
test-data/output1.tif
test-data/output2.tif
tests.xml
removed:
test-data/out.tiff
test-data/points.tsv
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d creators.xml
--- a/creators.xml Wed Jun 26 08:35:56 2024 +0000
+++ b/creators.xml Wed Sep 25 08:29:59 2024 +0000
b
@@ -5,6 +5,11 @@
         <yield />
     </xml>
 
+    <xml name="creators/rmassei">
+        <person givenName="Riccardo" familyName="Massei"/>
+        <yield/>
+    </xml>
+
     <xml name="creators/alliecreason">
         <person givenName="Allison" familyName="Creason"/>
         <yield/>
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d points2binaryimage.py
--- a/points2binaryimage.py Wed Jun 26 08:35:56 2024 +0000
+++ b/points2binaryimage.py Wed Sep 25 08:29:59 2024 +0000
[
@@ -1,36 +1,75 @@
 import argparse
 import os
 import warnings
+from typing import List
 
 import numpy as np
 import pandas as pd
+import scipy.ndimage as ndi
 import skimage.io
 
 
-def points2binaryimage(point_file, out_file, shape=[500, 500], has_header=False, invert_xy=False):
+def find_column(df: pd.DataFrame, candidates: List[str]) -> str:
+    """
+    Returns the column name present in `df` and the list of `candidates`.
 
-    img = np.zeros(shape, dtype=np.int16)
+    Raises:
+        KeyError: If there is no candidate column name present in `df`, or more than one.
+    """
+    intersection = frozenset(df.columns) & frozenset(candidates)
+    if len(intersection) == 0:
+        raise KeyError(f'No such column: {", ".join(candidates)}')
+    elif len(intersection) > 1:
+        raise KeyError(f'The column names {", ".join(intersection)} are ambiguous')
+    else:
+        return next(iter(intersection))
+
+
+def points2binaryimage(point_file, out_file, shape, has_header=False, swap_xy=False, bg_value=0, fg_value=0xffff):
+
+    img = np.full(shape, dtype=np.uint16, fill_value=bg_value)
     if os.path.exists(point_file) and os.path.getsize(point_file) > 0:
-        if has_header:
-            df = pd.read_csv(point_file, skiprows=1, header=None, delimiter="\t")
-        else:
-            df = pd.read_csv(point_file, header=None, delimiter="\t")
 
-        for i in range(0, len(df)):
-            a_row = df.iloc[i]
-            if int(a_row[0]) < 0 or int(a_row[1]) < 0:
-                raise IndexError("Point {},{} is out of image with bounds {},{}.".format(int(a_row[0]), int(a_row[1]), shape[0], shape[1]))
+        # Read the tabular file with information from the header
+        if has_header:
+            df = pd.read_csv(point_file, delimiter='\t')
+            pos_x_column = find_column(df, ['pos_x', 'POS_X'])
+            pos_y_column = find_column(df, ['pos_y', 'POS_Y'])
+            pos_x_list = df[pos_x_column].round().astype(int)
+            pos_y_list = df[pos_y_column].round().astype(int)
+            assert len(pos_x_list) == len(pos_y_list)
+            try:
+                radius_column = find_column(df, ['radius', 'RADIUS'])
+                radius_list = df[radius_column]
+            except KeyError:
+                radius_list = [0] * len(pos_x_list)
 
-            if invert_xy:
-                if img.shape[0] <= int(a_row[0]) or img.shape[1] <= int(a_row[1]):
-                    raise IndexError("Point {},{} is out of image with bounds {},{}.".format(int(a_row[0]), int(a_row[1]), shape[0], shape[1]))
-                else:
-                    img[int(a_row[1]), int(a_row[0])] = 32767
+        # Read the tabular file without header
+        else:
+            df = pd.read_csv(point_file, header=None, delimiter='\t')
+            pos_x_list = df[0].round().astype(int)
+            pos_y_list = df[1].round().astype(int)
+            assert len(pos_x_list) == len(pos_y_list)
+            radius_list = [0] * len(pos_x_list)
+
+        # Optionally swap the coordinates
+        if swap_xy:
+            pos_x_list, pos_y_list = pos_y_list, pos_x_list
+
+        # Perform the rasterization
+        for y, x, radius in zip(pos_y_list, pos_x_list, radius_list):
+
+            if y < 0 or x < 0 or y >= shape[0] or x >= shape[1]:
+                raise IndexError(f'The point x={x}, y={y} exceeds the bounds of the image (width: {shape[1]}, height: {shape[0]})')
+
+            if radius > 0:
+                mask = np.ones(shape, dtype=bool)
+                mask[y, x] = False
+                mask = (ndi.distance_transform_edt(mask) <= radius)
+                img[mask] = fg_value
             else:
-                if img.shape[0] <= int(a_row[1]) or img.shape[1] <= int(a_row[0]):
-                    raise IndexError("Point {},{} is out of image with bounds {},{}.".format(int(a_row[1]), int(a_row[0]), shape[0], shape[1]))
-                else:
-                    img[int(a_row[0]), int(a_row[1])] = 32767
+                img[y, x] = fg_value
+
     else:
         raise Exception("{} is empty or does not exist.".format(point_file))  # appropriate built-in error?
 
@@ -41,14 +80,14 @@
 
 if __name__ == "__main__":
     parser = argparse.ArgumentParser()
-    parser.add_argument('point_file', type=argparse.FileType('r'), help='label file')
+    parser.add_argument('point_file', type=argparse.FileType('r'), help='point file')
     parser.add_argument('out_file', type=str, help='out file (TIFF)')
     parser.add_argument('shapex', type=int, help='shapex')
     parser.add_argument('shapey', type=int, help='shapey')
-    parser.add_argument('--has_header', dest='has_header', default=False, help='set True if CSV has header')
-    parser.add_argument('--invert_xy', dest='invert_xy', default=False, help='invert x and y in CSV')
+    parser.add_argument('--has_header', dest='has_header', default=False, help='set True if point file has header')
+    parser.add_argument('--swap_xy', dest='swap_xy', default=False, help='Swap X and Y coordinates')
 
     args = parser.parse_args()
 
     # TOOL
-    points2binaryimage(args.point_file.name, args.out_file, [args.shapey, args.shapex], has_header=args.has_header, invert_xy=args.invert_xy)
+    points2binaryimage(args.point_file.name, args.out_file, (args.shapey, args.shapex), has_header=args.has_header, swap_xy=args.swap_xy)
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d points2binaryimage.xml
--- a/points2binaryimage.xml Wed Jun 26 08:35:56 2024 +0000
+++ b/points2binaryimage.xml Wed Sep 25 08:29:59 2024 +0000
[
@@ -2,8 +2,9 @@
     <description></description>
     <macros>
         <import>creators.xml</import>
-        <token name="@TOOL_VERSION@">0.2</token>
-        <token name="@VERSION_SUFFIX@">3</token>
+        <import>tests.xml</import>
+        <token name="@TOOL_VERSION@">0.3</token>
+        <token name="@VERSION_SUFFIX@">0</token>
     </macros>
     <creator>
         <expand macro="creators/bmcv" />
@@ -15,45 +16,68 @@
         <xref type="bio.tools">galaxy_image_analysis</xref>
     </xrefs>
     <requirements> 
-        <requirement type="package" version="0.14.2">scikit-image</requirement> 
-        <requirement type="package" version="1.15.4">numpy</requirement>
-        <requirement type="package" version="0.23.4">pandas</requirement>
-        <requirement type="package" version="2018.7">pytz</requirement><!--pandas seems to have additional requirements that are not automatically installed-->
-        <requirement type="package" version="2.5.2">python-dateutil</requirement>
-        <requirement type="package" version="0.15.1">tifffile</requirement>
+        <requirement type="package" version="0.21">scikit-image</requirement> 
+        <requirement type="package" version="1.26.4">numpy</requirement>
+        <requirement type="package" version="1.2.4">pandas</requirement>
+        <requirement type="package" version="2024.6.18">tifffile</requirement>
     </requirements>
-    <command detect_errors="aggressive">
-        <![CDATA[
-        python '$__tool_directory__/points2binaryimage.py' '$input' '$output' $shapex $shapey $has_header $invert_xy
-        ]]>
-    </command>
+    <command detect_errors="aggressive"><![CDATA[
+
+        python '$__tool_directory__/points2binaryimage.py'
+        '$input'
+        '$output'
+        $shapex
+        $shapey
+        $has_header
+        $swap_xy
+
+    ]]></command>
     <inputs> 
-        <param name="input" type="data" format="tabular" label="CSV point file"/> 
-        <param name="shapex" type="integer" value="500" optional="true" min="1" max="2000" label="Width of output image" />
-        <param name="shapey" type="integer" value="500" optional="true" min="1" max="2000" label="Height of output image" />
-        <param name="has_header" type="boolean" checked="false" truevalue="--has_header True" falsevalue="" optional="true" label="Does point file contain header?" /> 
-        <param name="invert_xy" type="boolean" checked="false" falsevalue="" truevalue="--invert_xy True" optional="true" label="Inverts x and y in CSV point file" />
+        <param name="input" type="data" format="tabular" label="Tabular list of points"/> 
+        <param name="shapex" type="integer" value="500" min="1" label="Width of output image" />
+        <param name="shapey" type="integer" value="500" min="1" label="Height of output image" />
+        <param name="has_header" type="boolean" checked="false" truevalue="--has_header True" falsevalue="" optional="true" label="Tabular list of points has header" /> 
+        <param name="swap_xy" type="boolean" checked="false" falsevalue="" truevalue="--swap_xy True" optional="true" label="Swap X and Y coordinates" />
     </inputs>
     <outputs>
         <data name="output" format="tiff" />
     </outputs>
     <tests>
+        <!-- TSV without header -->
         <test>
-            <param name="input" value="points.tsv" />
-            <param name="shapex" value="20" /> 
-            <param name="shapey" value="30" />
+            <param name="input" value="input1.tsv" />
+            <param name="shapex" value="30" />
+            <param name="shapey" value="20" />
             <param name="has_header" value="false" />
-            <param name="invert_xy" value="true" />
-            <output name="output" ftype="tiff" file="out.tiff" compare="sim_size"/> 
+            <param name="swap_xy" value="true" />
+            <expand macro="tests/binary_image_diff" name="output" value="output1.tif" ftype="tiff" />
+        </test>
+        <!-- TSV with header -->
+        <test>
+            <param name="input" value="input2.tsv" />
+            <param name="shapex" value="205" />
+            <param name="shapey" value="84" />
+            <param name="has_header" value="true" />
+            <param name="swap_xy" value="false" />
+            <expand macro="tests/binary_image_diff" name="output" value="output2.tif" ftype="tiff" />
         </test>
     </tests>
     <help>
 
-        This tool converts a CSV list of points to a binary image by rasterizing the point coordinates.
+        **Converts a tabular list of points to a binary image by rasterizing the point coordinates.**
 
         The created image is a single-channel image with 16 bits per pixel (unsigned integer).
-        The points are rasterized with value 32767 (white).
-        Pixels not corresponding to any points in the CSV are assigned the value 0 (black).
+        The points are rasterized with value 65535 (white).
+        Pixels not corresponding to any points in the tabular file are assigned the value 0 (black).
+
+        The tabular list of points can either be header-less.
+        In this case, the first and second columns are expected to be the X and Y coordinates, respectively.
+        Otherwise, if a header is present, it is searched for the following column names:
+
+        - ``pos_x`` or ``POS_X``: This column corresponds to the X coordinates.
+        - ``pos_y`` or ``POS_Y``: This column corresponds to the Y coordinates.
+        - If a ``radius`` or ``RADIUS`` column is present,
+          then the points will be rasterized as circles of the corresponding radii.
 
     </help>
     <citations>
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d test-data/input1.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input1.tsv Wed Sep 25 08:29:59 2024 +0000
b
@@ -0,0 +1,9 @@
+11.7555970149 10.4048507463
+15 14
+19 2
+5 4
+5 5
+5 6
+5 7
+5 8
+5 9
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d test-data/input2.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input2.tsv Wed Sep 25 08:29:59 2024 +0000
b
@@ -0,0 +1,39 @@
+frame pos_x pos_y scale radius intensity
+1 85 32 1.33 3.77 18807.73
+1 190 25 1.78 5.03 24581.44
+1 137 26 1.44 4.09 19037.59
+1 63 42 1.44 4.09 22390.80
+1 107 44 1.33 3.77 23429.96
+1 61 27 1.56 4.40 18052.18
+1 158 39 1.44 4.09 18377.02
+1 190 14 1.33 3.77 18548.86
+1 182 33 1.78 5.03 26467.79
+1 39 39 1.44 4.09 14782.43
+1 169 26 1.33 3.77 14203.41
+1 61 54 1.33 3.77 23248.06
+1 95 52 1.33 3.77 21480.71
+1 23 60 1.89 5.34 25203.43
+1 84 24 1.56 4.40 16630.57
+1 121 47 1.67 4.71 15459.11
+1 66 49 1.11 3.14 23858.07
+1 115 36 2.00 5.66 16389.10
+1 55 51 1.33 3.77 23548.90
+1 130 72 1.67 4.71 15769.02
+1 117 23 1.33 3.77 16763.14
+1 45 52 1.56 4.40 22877.61
+1 36 71 1.56 4.40 20780.96
+1 78 17 1.33 3.77 16844.51
+1 101 38 1.56 4.40 21376.59
+1 147 31 1.78 5.03 16597.14
+1 163 55 2.00 5.66 18301.54
+1 164 23 1.33 3.77 17073.82
+1 150 24 1.56 4.40 15440.02
+1 151 67 1.78 5.03 18419.96
+1 26 53 2.00 5.66 20586.01
+1 79 62 1.33 3.77 15232.88
+1 69 17 1.11 3.14 15601.83
+1 83 52 1.33 3.77 18315.00
+1 16 54 2.00 5.66 22140.66
+1 166 61 1.78 5.03 18488.78
+1 163 43 1.44 4.09 16925.49
+1 130 53 1.78 5.03 15101.96
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d test-data/out.tiff
b
Binary file test-data/out.tiff has changed
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d test-data/output1.original.tif
b
Binary file test-data/output1.original.tif has changed
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d test-data/output1.tif
b
Binary file test-data/output1.tif has changed
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d test-data/output2.tif
b
Binary file test-data/output2.tif has changed
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d test-data/points.tsv
--- a/test-data/points.tsv Wed Jun 26 08:35:56 2024 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
b
@@ -1,9 +0,0 @@
-11.7555970149 10.4048507463
-15 14
-19 2
-5 4
-5 5
-5 6
-5 7
-5 8
-5 9
b
diff -r a80f92ec6f89 -r 4c0f16fb9f8d tests.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests.xml Wed Sep 25 08:29:59 2024 +0000
b
@@ -0,0 +1,95 @@
+<macros>
+
+    <!-- Macros for verification of image outputs -->
+
+    <xml
+        name="tests/binary_image_diff"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="mae"
+        token_eps="0.01">
+
+        <output name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="0">
+            <assert_contents>
+                <has_image_n_labels n="2"/>
+                <yield/>
+            </assert_contents>
+        </output>
+
+    </xml>
+
+    <xml
+        name="tests/label_image_diff"
+        tokens="name,value,ftype,metric,eps,pin_labels"
+        token_metric="iou"
+        token_eps="0.01"
+        token_pin_labels="0">
+
+        <output name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="@PIN_LABELS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </output>
+
+    </xml>
+
+    <xml
+        name="tests/intensity_image_diff"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="rms"
+        token_eps="0.01">
+
+        <output name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </output>
+
+    </xml>
+
+    <!-- Variants of the above for verification of collection elements -->
+
+    <xml
+        name="tests/binary_image_diff/element"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="mae"
+        token_eps="0.01">
+
+        <element name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="0">
+            <assert_contents>
+                <has_image_n_labels n="2"/>
+                <yield/>
+            </assert_contents>
+        </element>
+
+    </xml>
+
+    <xml
+        name="tests/label_image_diff/element"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="iou"
+        token_eps="0.01"
+        token_pin_labels="0">
+
+        <element name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="@PIN_LABELS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </element>
+
+    </xml>
+
+    <xml
+        name="tests/intensity_image_diff/element"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="rms"
+        token_eps="0.01">
+
+        <element name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </element>
+
+    </xml>
+
+</macros>