diff 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
line wrap: on
line diff
--- a/points2label.py	Wed Apr 23 14:37:31 2025 +0000
+++ b/points2label.py	Mon May 12 14:01:09 2025 +0000
@@ -1,15 +1,82 @@
 import argparse
+import json
 import os
 import warnings
+from typing import (
+    Dict,
+    List,
+    Tuple,
+    Union,
+)
 
 import giatools.pandas
 import numpy as np
+import numpy.typing as npt
 import pandas as pd
 import scipy.ndimage as ndi
 import skimage.io
 import skimage.segmentation
 
 
+def is_rectangular(points: Union[List[Tuple[float, float]], npt.NDArray]) -> bool:
+    points = np.asarray(points)
+
+    # Rectangle must have 5 points, where first and last are identical
+    if len(points) != 5 or not (points[0] == points[-1]).all():
+        return False
+
+    # Check that all edges align with the axes
+    edges = points[1:] - points[:-1]
+    if any((edge == 0).sum() != 1 for edge in edges):
+        return False
+
+    # All checks have passed, the geometry is rectangular
+    return True
+
+
+def geojson_to_tabular(geojson: Dict):
+    rows = []
+    labels = []
+    for feature in geojson['features']:
+        assert feature['geometry']['type'].lower() == 'polygon', (
+            f'Unsupported geometry type: "{feature["geometry"]["type"]}"'
+        )
+        coords = feature['geometry']['coordinates'][0]
+
+        # Properties and name (label) are optional
+        try:
+            label = feature['properties']['name']
+        except KeyError:
+            label = max(labels, default=0) + 1
+        labels.append(label)
+
+        # Read geometry
+        xs = [pt[0] for pt in coords]
+        ys = [pt[1] for pt in coords]
+
+        x = min(xs)
+        y = min(ys)
+
+        width = max(xs) + 1 - x
+        height = max(ys) + 1 - y
+
+        # Validate geometry (must be rectangular)
+        assert is_rectangular(list(zip(xs, ys)))
+
+        # Append the rectangle
+        rows.append({
+            'pos_x': x,
+            'pos_y': y,
+            'width': width,
+            'height': height,
+            'label': label,
+        })
+    df = pd.DataFrame(rows)
+    point_file = './point_file.tabular'
+    df.to_csv(point_file, sep='\t', index=False)
+    return point_file
+
+
 def rasterize(point_file, out_file, shape, has_header=False, swap_xy=False, bg_value=0, fg_value=None):
 
     img = np.full(shape, dtype=np.uint16, fill_value=bg_value)
@@ -122,16 +189,16 @@
                 img[y, x] = label
 
     else:
-        raise Exception("{} is empty or does not exist.".format(point_file))  # appropriate built-in error?
+        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__":
+if __name__ == '__main__':
     parser = argparse.ArgumentParser()
-    parser.add_argument('point_file', type=argparse.FileType('r'), help='point file')
+    parser.add_argument('in_file', type=argparse.FileType('r'), help='Input point file or GeoJSON 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')
@@ -141,11 +208,25 @@
 
     args = parser.parse_args()
 
+    point_file = args.in_file.name
+    has_header = args.has_header
+
+    try:
+        with open(args.in_file.name, 'r') as f:
+            content = json.load(f)
+            if isinstance(content, dict) and content.get('type') == 'FeatureCollection' and isinstance(content.get('features'), list):
+                point_file = geojson_to_tabular(content)
+                has_header = True  # header included in the converted file
+            else:
+                raise ValueError('Input is a JSON file but not a valid GeoJSON file')
+    except json.JSONDecodeError:
+        print('Input is not a valid JSON file. Assuming it a tabular file.')
+
     rasterize(
-        args.point_file.name,
+        point_file,
         args.out_file,
         (args.shapey, args.shapex),
-        has_header=args.has_header,
+        has_header=has_header,
         swap_xy=args.swap_xy,
         fg_value=0xffff if args.binary else None,
     )