Mercurial > repos > imgteam > points2labelimage
changeset 3:de611b3b5ae8 draft default tip
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/points2labelimage/ commit 6fc9ab8db9ef72ac7ded30d7373768feeae9390d
author | imgteam |
---|---|
date | Fri, 27 Sep 2024 17:41:21 +0000 |
parents | 30ca5d5d03ec |
children | |
files | creators.xml points2label.py points2label.xml test-data/galaxyIcon_noText.tif test-data/input1.tsv test-data/input2.tsv test-data/input3.tsv test-data/out.tif test-data/output1_binary.tif test-data/output2.tif test-data/output2_binary.tif test-data/output3.tif test-data/output3_binary.tif test-data/points.tsv tests.xml |
diffstat | 15 files changed, 382 insertions(+), 56 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/creators.xml Fri Sep 27 17:41:21 2024 +0000 @@ -0,0 +1,28 @@ +<macros> + + <xml name="creators/bmcv"> + <organization name="Biomedical Computer Vision Group, Heidelberg Universtiy" alternateName="BMCV" url="http://www.bioquant.uni-heidelberg.de/research/groups/biomedical_computer_vision.html" /> + <yield /> + </xml> + + <xml name="creators/rmassei"> + <person givenName="Riccardo" familyName="Massei"/> + <yield/> + </xml> + + <xml name="creators/alliecreason"> + <person givenName="Allison" familyName="Creason"/> + <yield/> + </xml> + + <xml name="creators/bugraoezdemir"> + <person givenName="Bugra" familyName="Oezdemir"/> + <yield/> + </xml> + + <xml name="creators/thawn"> + <person givenName="Till" familyName="Korten"/> + <yield/> + </xml> + +</macros>
--- a/points2label.py Mon Nov 13 22:11:46 2023 +0000 +++ b/points2label.py Fri Sep 27 17:41:21 2024 +0000 @@ -1,47 +1,125 @@ import argparse -import sys +import os import warnings +import giatools.pandas import numpy as np import pandas as pd +import scipy.ndimage as ndi import skimage.io +import skimage.segmentation -def points2label(labels, shape, output_file=None, has_header=False, is_TSV=True): - labelimg = np.zeros([shape[0], shape[1]], dtype=np.int32) +def rasterize(point_file, out_file, shape, has_header=False, swap_xy=False, bg_value=0, fg_value=None): - if is_TSV: + img = np.full(shape, dtype=np.uint16, fill_value=bg_value) + if os.path.exists(point_file) and os.path.getsize(point_file) > 0: + + # Read the tabular file with information from the header if has_header: - df = pd.read_csv(labels, sep='\t', skiprows=1, header=None) - else: - df = pd.read_csv(labels, sep='\t', header=None) - else: - if has_header: - df = pd.read_csv(labels, skiprows=1, header=None) + df = pd.read_csv(point_file, delimiter='\t') + + pos_x_column = giatools.pandas.find_column(df, ['pos_x', 'POS_X']) + pos_y_column = giatools.pandas.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 = giatools.pandas.find_column(df, ['radius', 'RADIUS']) + radius_list = df[radius_column] + assert len(pos_x_list) == len(radius_list) + except KeyError: + radius_list = [0] * len(pos_x_list) + + try: + label_column = giatools.pandas.find_column(df, ['label', 'LABEL']) + label_list = df[label_column] + assert len(pos_x_list) == len(label_list) + except KeyError: + label_list = list(range(1, len(pos_x_list) + 1)) + + # Read the tabular file without header else: - df = pd.read_csv(labels, header=None) + 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) + label_list = list(range(1, len(pos_x_list) + 1)) + + # Optionally swap the coordinates + if swap_xy: + pos_x_list, pos_y_list = pos_y_list, pos_x_list - for i in range(0, len(df)): - a_row = df.iloc[i] - labelimg[a_row[0], a_row[1]] = i + 1 + # Perform the rasterization + for y, x, radius, label in zip(pos_y_list, pos_x_list, radius_list, label_list): + if fg_value is not None: + label = fg_value + + 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]})') + + # Rasterize circle and distribute overlapping image area + if radius > 0: + mask = np.ones(shape, dtype=bool) + mask[y, x] = False + mask = (ndi.distance_transform_edt(mask) <= radius) - if output_file is not None: - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - skimage.io.imsave(output_file, labelimg, plugin='tifffile') + # Compute the overlap (pretend there is none if the rasterization is binary) + if fg_value is None: + overlap = np.logical_and(img > 0, mask) + else: + overlap = np.zeros(shape, dtype=bool) + + # Rasterize the part of the circle which is disjoint from other foreground. + # + # In the current implementation, the result depends on the order of the rasterized circles if somewhere + # more than two circles overlap. This is probably negligable for most applications. To achieve results + # that are invariant to the order, first all circles would need to be rasterized independently, and + # then blended together. This, however, would either strongly increase the memory consumption, or + # require a more complex implementation which exploits the sparsity of the rasterized masks. + # + disjoint_mask = np.logical_xor(mask, overlap) + if disjoint_mask.any(): + img[disjoint_mask] = label + + # Distribute the remaining part of the circle + if overlap.any(): + dist = ndi.distance_transform_edt(overlap) + foreground = (img > 0) + img[overlap] = 0 + img = skimage.segmentation.watershed(dist, img, mask=foreground) + + # Rasterize point (there is no overlapping area to be distributed) + else: + img[y, x] = label + else: - return labelimg + raise Exception("{} is empty or does not exist.".format(point_file)) # appropriate built-in error? + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + skimage.io.imsave(out_file, img, plugin='tifffile') # otherwise we get problems with the .dat extension if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('label_file', type=argparse.FileType('r'), default=sys.stdin, help='label file') - parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin, help='out file') - parser.add_argument('org_file', type=argparse.FileType('r'), default=sys.stdin, help='input original file') - parser.add_argument('--has_header', dest='has_header', type=bool, default=False, help='label file has header') - parser.add_argument('--is_tsv', dest='is_tsv', type=bool, default=True, help='label file is TSV') + 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 point file has header') + parser.add_argument('--swap_xy', dest='swap_xy', default=False, help='Swap X and Y coordinates') + parser.add_argument('--binary', dest='binary', default=False, help='Produce binary image') + args = parser.parse_args() - original_shape = skimage.io.imread(args.org_file.name, plugin='tifffile').shape - - points2label(args.label_file.name, original_shape, args.out_file.name, args.has_header, args.is_tsv) + rasterize( + args.point_file.name, + args.out_file, + (args.shapey, args.shapex), + has_header=args.has_header, + swap_xy=args.swap_xy, + fg_value=0xffff if args.binary else None, + )
--- a/points2label.xml Mon Nov 13 22:11:46 2023 +0000 +++ b/points2label.xml Fri Sep 27 17:41:21 2024 +0000 @@ -1,41 +1,121 @@ -<tool id="ip_points_to_label" name="Convert point coordinates to label map" version="0.3-2"> +<tool id="ip_points_to_label" name="Convert point coordinates to label map" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="20.05"> <description></description> + <macros> + <import>creators.xml</import> + <import>tests.xml</import> + <token name="@TOOL_VERSION@">0.4</token> + <token name="@VERSION_SUFFIX@">0</token> + </macros> + <creator> + <expand macro="creators/bmcv" /> + </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="1.15.4">numpy</requirement> - <requirement type="package" version="0.14.2">scikit-image</requirement> - <requirement type="package" version="0.23.4">pandas</requirement> - <requirement type="package" version="0.15.1">tifffile</requirement> - </requirements> - <command detect_errors="aggressive"> - <![CDATA[ - python '$__tool_directory__/points2label.py' '$input' '$output' '$org_file' $has_header - ]]> - </command> - <inputs> - <param name="input" type="data" format="tabular" label="Point CSV file"/> - <param name="org_file" type="data" format="tiff" label="Original label image file"/> - <param name="has_header" type="boolean" checked="false" truevalue="--has_header True" falsevalue="" optional="true" label="Does point file contain header?" /> + <requirements> + <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> + <requirement type="package" version="0.3.1">giatools</requirement> + </requirements> + <command detect_errors="aggressive"><![CDATA[ + + python '$__tool_directory__/points2label.py' + '$input' + '$output' + $shapex + $shapey + $has_header + $swap_xy + $binary + + ]]></command> + <inputs> + <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="true" truevalue="--has_header True" falsevalue="" optional="true" label="Tabular list of points has header" help="Turning this off will ignore the first row and assume that the X and Y coordinates correspond to the first and second column, respectively." /> + <param name="swap_xy" type="boolean" checked="false" falsevalue="" truevalue="--swap_xy True" optional="true" label="Swap X and Y coordinates" help="Swap the X and Y coordinates, regardless of whether the tabular list has a header or not." /> + <param name="binary" type="boolean" checked="false" truevalue="--binary True" falsevalue="" optional="true" label="Produce binary image" help="Use the same label for all points (65535)." /> </inputs> <outputs> - <data name="output" format="tiff"/> + <data name="output" format="tiff" /> </outputs> <tests> + <!-- Binary / TSV without header --> <test> - <param name="input" value="points.tsv"/> - <param name="org_file" value="galaxyIcon_noText.tif"/> - <output name="output" file="out.tif" ftype="tiff" compare="sim_size"/> + <param name="input" value="input1.tsv" /> + <param name="shapex" value="30" /> + <param name="shapey" value="20" /> + <param name="has_header" value="false" /> + <param name="swap_xy" value="true" /> + <param name="binary" value="true" /> + <expand macro="tests/binary_image_diff" name="output" value="output1_binary.tif" ftype="tiff" /> + </test> + <!-- Binary / 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" /> + <param name="binary" value="true" /> + <expand macro="tests/binary_image_diff" name="output" value="output2_binary.tif" ftype="tiff" /> + </test> + <!-- Labeled / 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" /> + <param name="binary" value="false" /> + <expand macro="tests/label_image_diff" name="output" value="output2.tif" ftype="tiff" /> + </test> + <!-- Binary / TSV with header / TSV with labels --> + <test> + <param name="input" value="input3.tsv" /> + <param name="shapex" value="200" /> + <param name="shapey" value="100" /> + <param name="has_header" value="true" /> + <param name="swap_xy" value="false" /> + <param name="binary" value="true" /> + <expand macro="tests/binary_image_diff" name="output" value="output3_binary.tif" ftype="tiff" /> + </test> + <!-- Labeled / TSV with header / TSV with labels --> + <test> + <param name="input" value="input3.tsv" /> + <param name="shapex" value="200" /> + <param name="shapey" value="100" /> + <param name="has_header" value="true" /> + <param name="swap_xy" value="false" /> + <param name="binary" value="false" /> + <expand macro="tests/label_image_diff" name="output" value="output3.tif" ftype="tiff" /> </test> </tests> <help> - **What it does** + + **Converts a tabular list of points to a label map 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 unique labels, or the value 65535 (white) for binary image output. Pixels not corresponding to + any points in the tabular file are assigned the value 0 (black). - This tool converts points to a label image. + 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. + - If a ``label`` or ``LABEL`` column is present, then the corresponding labels will be used for rasterization + (unless "Produce binary image" is activated). Different points are allowed to use the same label. + </help> <citations> <citation type="doi">10.1016/j.jbiotec.2017.07.019</citation>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input1.tsv Fri Sep 27 17:41:21 2024 +0000 @@ -0,0 +1,9 @@ +11.7555970149 10.4048507463 +15 14 +19 2 +5 4 +5 5 +5 6 +5 7 +5 8 +5 9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input2.tsv Fri Sep 27 17:41:21 2024 +0000 @@ -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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input3.tsv Fri Sep 27 17:41:21 2024 +0000 @@ -0,0 +1,4 @@ +pos_x pos_y radius label +20 20 20 1 +50 50 40 1 +150 50 40 2
--- a/test-data/points.tsv Mon Nov 13 22:11:46 2023 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -5 3 -10 2 -8 4 -15 15 -8 5 -8 6 -8 7
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests.xml Fri Sep 27 17:41:21 2024 +0000 @@ -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>