comparison points2label.py @ 4:64c155acb864 draft default tip

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
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 )