Mercurial > repos > imgteam > points2labelimage
comparison points2label.py @ 4:64c155acb864 draft
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/points2labelimage/ commit 9a40a5d1e1008c26cc327c6d163df2a1af22a1a0
| author | imgteam |
|---|---|
| date | Mon, 12 May 2025 14:01:09 +0000 |
| parents | 2ae122d5d85a |
| children | dc91192a7150 |
comparison
equal
deleted
inserted
replaced
| 3:2ae122d5d85a | 4:64c155acb864 |
|---|---|
| 1 import argparse | 1 import argparse |
| 2 import json | |
| 2 import os | 3 import os |
| 3 import warnings | 4 import warnings |
| 5 from typing import ( | |
| 6 Dict, | |
| 7 List, | |
| 8 Tuple, | |
| 9 Union, | |
| 10 ) | |
| 4 | 11 |
| 5 import giatools.pandas | 12 import giatools.pandas |
| 6 import numpy as np | 13 import numpy as np |
| 14 import numpy.typing as npt | |
| 7 import pandas as pd | 15 import pandas as pd |
| 8 import scipy.ndimage as ndi | 16 import scipy.ndimage as ndi |
| 9 import skimage.io | 17 import skimage.io |
| 10 import skimage.segmentation | 18 import skimage.segmentation |
| 19 | |
| 20 | |
| 21 def is_rectangular(points: Union[List[Tuple[float, float]], npt.NDArray]) -> bool: | |
| 22 points = np.asarray(points) | |
| 23 | |
| 24 # Rectangle must have 5 points, where first and last are identical | |
| 25 if len(points) != 5 or not (points[0] == points[-1]).all(): | |
| 26 return False | |
| 27 | |
| 28 # Check that all edges align with the axes | |
| 29 edges = points[1:] - points[:-1] | |
| 30 if any((edge == 0).sum() != 1 for edge in edges): | |
| 31 return False | |
| 32 | |
| 33 # All checks have passed, the geometry is rectangular | |
| 34 return True | |
| 35 | |
| 36 | |
| 37 def geojson_to_tabular(geojson: Dict): | |
| 38 rows = [] | |
| 39 labels = [] | |
| 40 for feature in geojson['features']: | |
| 41 assert feature['geometry']['type'].lower() == 'polygon', ( | |
| 42 f'Unsupported geometry type: "{feature["geometry"]["type"]}"' | |
| 43 ) | |
| 44 coords = feature['geometry']['coordinates'][0] | |
| 45 | |
| 46 # Properties and name (label) are optional | |
| 47 try: | |
| 48 label = feature['properties']['name'] | |
| 49 except KeyError: | |
| 50 label = max(labels, default=0) + 1 | |
| 51 labels.append(label) | |
| 52 | |
| 53 # Read geometry | |
| 54 xs = [pt[0] for pt in coords] | |
| 55 ys = [pt[1] for pt in coords] | |
| 56 | |
| 57 x = min(xs) | |
| 58 y = min(ys) | |
| 59 | |
| 60 width = max(xs) + 1 - x | |
| 61 height = max(ys) + 1 - y | |
| 62 | |
| 63 # Validate geometry (must be rectangular) | |
| 64 assert is_rectangular(list(zip(xs, ys))) | |
| 65 | |
| 66 # Append the rectangle | |
| 67 rows.append({ | |
| 68 'pos_x': x, | |
| 69 'pos_y': y, | |
| 70 'width': width, | |
| 71 'height': height, | |
| 72 'label': label, | |
| 73 }) | |
| 74 df = pd.DataFrame(rows) | |
| 75 point_file = './point_file.tabular' | |
| 76 df.to_csv(point_file, sep='\t', index=False) | |
| 77 return point_file | |
| 11 | 78 |
| 12 | 79 |
| 13 def rasterize(point_file, out_file, shape, has_header=False, swap_xy=False, bg_value=0, fg_value=None): | 80 def rasterize(point_file, out_file, shape, has_header=False, swap_xy=False, bg_value=0, fg_value=None): |
| 14 | 81 |
| 15 img = np.full(shape, dtype=np.uint16, fill_value=bg_value) | 82 img = np.full(shape, dtype=np.uint16, fill_value=bg_value) |
| 120 # Rasterize point (there is no overlapping area to be distributed) | 187 # Rasterize point (there is no overlapping area to be distributed) |
| 121 else: | 188 else: |
| 122 img[y, x] = label | 189 img[y, x] = label |
| 123 | 190 |
| 124 else: | 191 else: |
| 125 raise Exception("{} is empty or does not exist.".format(point_file)) # appropriate built-in error? | 192 raise Exception('{} is empty or does not exist.'.format(point_file)) # appropriate built-in error? |
| 126 | 193 |
| 127 with warnings.catch_warnings(): | 194 with warnings.catch_warnings(): |
| 128 warnings.simplefilter("ignore") | 195 warnings.simplefilter("ignore") |
| 129 skimage.io.imsave(out_file, img, plugin='tifffile') # otherwise we get problems with the .dat extension | 196 skimage.io.imsave(out_file, img, plugin='tifffile') # otherwise we get problems with the .dat extension |
| 130 | 197 |
| 131 | 198 |
| 132 if __name__ == "__main__": | 199 if __name__ == '__main__': |
| 133 parser = argparse.ArgumentParser() | 200 parser = argparse.ArgumentParser() |
| 134 parser.add_argument('point_file', type=argparse.FileType('r'), help='point file') | 201 parser.add_argument('in_file', type=argparse.FileType('r'), help='Input point file or GeoJSON file') |
| 135 parser.add_argument('out_file', type=str, help='out file (TIFF)') | 202 parser.add_argument('out_file', type=str, help='out file (TIFF)') |
| 136 parser.add_argument('shapex', type=int, help='shapex') | 203 parser.add_argument('shapex', type=int, help='shapex') |
| 137 parser.add_argument('shapey', type=int, help='shapey') | 204 parser.add_argument('shapey', type=int, help='shapey') |
| 138 parser.add_argument('--has_header', dest='has_header', default=False, help='set True if point file has header') | 205 parser.add_argument('--has_header', dest='has_header', default=False, help='set True if point file has header') |
| 139 parser.add_argument('--swap_xy', dest='swap_xy', default=False, help='Swap X and Y coordinates') | 206 parser.add_argument('--swap_xy', dest='swap_xy', default=False, help='Swap X and Y coordinates') |
| 140 parser.add_argument('--binary', dest='binary', default=False, help='Produce binary image') | 207 parser.add_argument('--binary', dest='binary', default=False, help='Produce binary image') |
| 141 | 208 |
| 142 args = parser.parse_args() | 209 args = parser.parse_args() |
| 143 | 210 |
| 211 point_file = args.in_file.name | |
| 212 has_header = args.has_header | |
| 213 | |
| 214 try: | |
| 215 with open(args.in_file.name, 'r') as f: | |
| 216 content = json.load(f) | |
| 217 if isinstance(content, dict) and content.get('type') == 'FeatureCollection' and isinstance(content.get('features'), list): | |
| 218 point_file = geojson_to_tabular(content) | |
| 219 has_header = True # header included in the converted file | |
| 220 else: | |
| 221 raise ValueError('Input is a JSON file but not a valid GeoJSON file') | |
| 222 except json.JSONDecodeError: | |
| 223 print('Input is not a valid JSON file. Assuming it a tabular file.') | |
| 224 | |
| 144 rasterize( | 225 rasterize( |
| 145 args.point_file.name, | 226 point_file, |
| 146 args.out_file, | 227 args.out_file, |
| 147 (args.shapey, args.shapex), | 228 (args.shapey, args.shapex), |
| 148 has_header=args.has_header, | 229 has_header=has_header, |
| 149 swap_xy=args.swap_xy, | 230 swap_xy=args.swap_xy, |
| 150 fg_value=0xffff if args.binary else None, | 231 fg_value=0xffff if args.binary else None, |
| 151 ) | 232 ) |
