Skip to content

Commit 2eaf9bb

Browse files
authored
Upgrade conversion of Core ML Crop (#249)
1 parent e2356b7 commit 2eaf9bb

File tree

2 files changed

+59
-10
lines changed

2 files changed

+59
-10
lines changed

onnxmltools/convert/common/_apply_operation.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# best to use those functions because they can produce ONNX operators according to the ONNX version specified in the
88
# `container` argument. Notice that those function behaviors are defined in a way very similar to ONNX-1.2.
99

10+
import numpy as np
1011
from ...proto import onnx_proto
1112

1213
def _create_name_or_use_existing_one(scope, op_type, name):
@@ -438,3 +439,46 @@ def apply_softmax(scope, input_name, output_name, container, operator_name=None,
438439
def apply_normalization(scope, input_name, output_name, container, operator_name=None, axis=1, p=2):
439440
name = _create_name_or_use_existing_one(scope, 'LpNormalization', operator_name)
440441
container.add_node('LpNormalization', input_name, output_name, name=name, p=p, axis=axis)
442+
443+
def apply_crop_height_width(scope, input_name, output_name, container, operator_name=None,
444+
top_border=0, bottom_border=0, left_border=0, right_border=0):
445+
name = scope.get_unique_operator_name('CropHeightWidth')
446+
if container.target_opset < 9:
447+
# If operator set < 9, we can use the experimental Crop in ONNX.
448+
attrs = {'name': name, 'border': [left_border, top_border, right_border, bottom_border]}
449+
container.add_node('Crop', input_name, output_name, **attrs)
450+
else:
451+
# The experimental Crop in ONNX is removed after operator set 9, so we
452+
# switch to ONNX DynamicSlice operator.
453+
454+
# CoreML only crops H- and W-axes.
455+
axes = [2, 3]
456+
axes_name = scope.get_unique_variable_name(name + '_axes')
457+
container.add_initializer(axes_name, onnx_proto.TensorProto.INT64,
458+
[len(axes)], axes)
459+
460+
# Number of cropped pixels is the starting index of the remained region.
461+
starts = [top_border, left_border]
462+
starts_name = scope.get_unique_variable_name(name + '_starts')
463+
container.add_initializer(starts_name, onnx_proto.TensorProto.INT64,
464+
[len(starts)], starts)
465+
466+
# First we assume no cropping is needed at the end of those axes.
467+
# We will change this right below depending on Crop's configuration.
468+
ends = [np.iinfo(np.int64).max] * 2
469+
470+
# Crop n pixel means the end index (exclusive) is -n. Note that indexing
471+
# system is zero-based.
472+
if bottom_border > 0:
473+
ends[0] = -bottom_border
474+
if right_border > 0:
475+
ends[1] = -right_border
476+
477+
# Add the adjusted ends.
478+
ends_name = scope.get_unique_variable_name(name + '_ends')
479+
container.add_initializer(ends_name, onnx_proto.TensorProto.INT64,
480+
[len(ends)], ends)
481+
482+
# Collect all input names as a list because DynamicSlice has multiple inputs.
483+
input_list = [input_name, starts_name, ends_name, axes_name]
484+
container.add_node('DynamicSlice', input_list, output_name, op_version=9)

onnxmltools/convert/coreml/operator_converters/neural_network/Crop.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
# license information.
55
# --------------------------------------------------------------------------
66

7+
import numpy as np
8+
from .....proto import onnx_proto
9+
from ....common._apply_operation import apply_crop_height_width
710
from ....common._registration import register_converter
811

9-
1012
def convert_crop(scope, operator, container):
11-
if len(operator.input_full_names) > 2:
12-
raise RuntimeError('Unlike CoreML, ONNX only supports cropping with a single input')
13-
14-
op_type = 'Crop'
15-
attrs = {'name': operator.full_name}
13+
# Extract number of pixels cropped in CoreML operator.
1614
border = operator.raw_operator.crop.cropAmounts.borderAmounts
17-
left_border, top_border, right_border, bottom_border = (None,) * 4
18-
if len(border):
15+
16+
# Compute cropping amounts. left_border=1 means one pixel will be removed at the beginning
17+
# of W-axis. bottom_border=1 means one pixel will be removed at the end of H-axis.
18+
left_border, top_border, right_border, bottom_border = (0,) * 4
19+
if len(operator.input_full_names) == 1:
1920
left_border = border[1].startEdgeSize
2021
top_border = border[0].startEdgeSize
2122
right_border = border[1].endEdgeSize
@@ -29,9 +30,13 @@ def convert_crop(scope, operator, container):
2930
right_border = in_shape[3] - left_border - out_shape[3]
3031
bottom_border = in_shape[2] - top_border - out_shape[2]
3132

32-
attrs['border'] = [left_border, top_border, right_border, bottom_border]
33+
# Delegate the selection of ONNX operator to a version-dependent function.
34+
apply_crop_height_width(scope,
35+
operator.input_full_names[0], operator.output_full_names[0],
36+
container, operator_name=operator.full_name,
37+
top_border=top_border, bottom_border=bottom_border,
38+
left_border=left_border, right_border=right_border)
3339

34-
container.add_node(op_type, operator.input_full_names[0:1], operator.output_full_names, **attrs)
3540

3641

3742
register_converter('crop', convert_crop)

0 commit comments

Comments
 (0)