Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 35 additions & 18 deletions tools/points2labelimage/points2label.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import (
Any,
Dict,
Iterator,
Optional,
Tuple,
)
Expand Down Expand Up @@ -52,11 +53,11 @@ def next(self):
self.next_autolabel += 1


def get_feature_label(feature: Dict) -> Optional[int]:
def get_feature_label(feature: Dict, label_property: str) -> Optional[int]:
"""
Get the label of a GeoJSON feature, or `None` if there is no proper label.
"""
label = feature.get('properties', {}).get('name', None)
label = feature.get('properties', {}).get(label_property, None)
if label is None:
return None

Expand All @@ -79,7 +80,8 @@ def rasterize(
shape: Tuple[int, int],
bg_value: int = 0,
fg_value: Optional[int] = None,
) -> npt.NDArray:
label_property: str = 'name',
) -> Iterator[Tuple[npt.NDArray, int]]:
"""
Rasterize GeoJSON into a pixel image, that is returned as a NumPy array.
"""
Expand All @@ -88,7 +90,7 @@ def rasterize(
reserved_labels = [bg_value]
if fg_value is None:
for feature in geojson['features']:
label = get_feature_label(feature)
label = get_feature_label(feature, label_property)
if label is not None:
reserved_labels.append(label)

Expand All @@ -99,7 +101,6 @@ def rasterize(
autolabel = AutoLabel(reserved_labels)

# Rasterize the image
img = np.full(shape, dtype=np.uint16, fill_value=bg_value)
for feature in geojson['features']:
geom_type = feature['geometry']['type'].lower()
coords = feature['geometry']['coordinates']
Expand Down Expand Up @@ -134,17 +135,14 @@ def rasterize(

# Determine the `label` for the current `mask`
if fg_value is None:
label = get_feature_label(feature)
label = get_feature_label(feature, label_property)
if label is None:
label = autolabel.next()
else:
label = fg_value

# Blend the current `mask` with the rasterized image
img[mask] = label

# Return the rasterized image
return img
# Yield the current `mask` and `label`
yield mask, label


def convert_tabular_to_geojson(
Expand Down Expand Up @@ -255,11 +253,19 @@ def convert_tabular_to_geojson(
parser.add_argument('out_file', type=str, help='Output file path (TIFF)')
parser.add_argument('shapex', type=int, help='Output image width')
parser.add_argument('shapey', type=int, help='Output image height')
parser.add_argument('--has_header', dest='has_header', default=False, help='Set True if tabular file has a 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')
parser.add_argument('--bg_value', type=int, default=0, help='Label used for image background')
parser.add_argument('--label_property', type=str, default='name', help='GeoJSON property used as labels')
parser.add_argument('--has_header', default=False, help='Set if tabular file has a header', action='store_true')
parser.add_argument('--swap_xy', default=False, help='Swap X and Y coordinates', action='store_true')
parser.add_argument('--binary', default=False, help='Produce binary image', action='store_true')
parser.add_argument('--split', default=False, help='Split rasterizations into one file per object', action='store_true')
args = parser.parse_args()

# Determine target shape
shape = (args.shapey, args.shapex)
if args.swap_xy:
shape = shape[::-1]

# Validate command-line arguments
assert args.in_ext in ('tabular', 'geojson'), (
f'Unexpected input file format: {args.in_ext}'
Expand All @@ -273,12 +279,23 @@ def convert_tabular_to_geojson(
geojson = json.load(f)

# Rasterize the image from GeoJSON
shape = (args.shapey, args.shapex)
img = rasterize(
img = None
for mask, label in rasterize(
geojson,
shape if not args.swap_xy else shape[::-1],
shape,
bg_value=args.bg_value,
fg_value=0xffff if args.binary else None,
)
label_property=args.label_property,
):
if args.split or img is None:
img = np.full(shape, dtype=np.uint16, fill_value=args.bg_value)

# Blend the rasterization into the target image
img[mask] = label

# TODO: write image to file if `args.split`

# Swap axes if requested
if args.swap_xy:
img = img.T

Expand Down
18 changes: 12 additions & 6 deletions tools/points2labelimage/points2label.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<macros>
<import>creators.xml</import>
<import>tests.xml</import>
<token name="@TOOL_VERSION@">0.5.0</token>
<token name="@TOOL_VERSION@">0.6.0</token>
</macros>
<creator>
<expand macro="creators/bmcv" />
Expand All @@ -24,23 +24,29 @@
<command detect_errors="aggressive"><![CDATA[

python '$__tool_directory__/points2label.py'

'$input.file_ext'
'$input'
'$output'

$shapex
$shapey

$has_header
$swap_xy
$binary

]]></command>
<inputs>
<param name="input" type="data" format="tabular,geojson" label="Shapes to be rasterized"/>
<param name="shapex" type="integer" value="500" min="1" label="Width of the output image" />
<param name="shapey" type="integer" value="500" min="1" label="Height of the output image" />
<param name="has_header" type="boolean" checked="true" truevalue="--has_header True" falsevalue="" optional="true" label="Tabular list of shapes has header" help="Only used if the input is a tabular file (ignored for GeoJSON). Turning this off will interpret the tabular file as a list of points, where 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 before rasterization. The width and height of the output image is not affected." />
<param name="binary" type="boolean" checked="false" truevalue="--binary True" falsevalue="" optional="true" label="Produce binary image" help="Use the same label for all shapes (65535)." />
<param name="shapex" type="integer" value="500" min="1" label="Width of the output image"/>
<param name="shapey" type="integer" value="500" min="1" label="Height of the output image"/>
<param name="has_header" type="boolean" checked="true" truevalue="--has_header" falsevalue="" label="Tabular list of shapes has header"
help="Only used if the input is a tabular file (ignored for GeoJSON). Turning this off will interpret the tabular file as a list of points, where 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" label="Swap X and Y coordinates"
help="Swap the X and Y coordinates before rasterization. The width and height of the output image is not affected."/>
<param name="binary" type="boolean" checked="false" truevalue="--binary" falsevalue="" label="Produce binary image(s)"
help="Use the same label for all shapes (65535)."/>
</inputs>
<outputs>
<data name="output" format="tiff" />
Expand Down
Loading