Skip to content

Commit 71bf4ae

Browse files
Add Cropping layers support (#1309)
* added Cropping1D and Cropping2D keras layers support * removed .bak templates files * added cropping layers tests for vivado and vitis * [pre-commit.ci] auto fixes from pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 94ea9be commit 71bf4ae

File tree

6 files changed

+422
-1
lines changed

6 files changed

+422
-1
lines changed

hls4ml/backends/vivado/passes/reshaping_templates.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate
2-
from hls4ml.model.layers import Resize, Transpose, ZeroPadding1D, ZeroPadding2D
2+
from hls4ml.model.layers import Cropping1D, Cropping2D, Resize, Transpose, ZeroPadding1D, ZeroPadding2D
33
from hls4ml.utils.transpose_utils import transpose_config_gen
44

55
# ZeroPadding templates
@@ -145,3 +145,61 @@ def format(self, node):
145145
params = self._default_function_params(node)
146146
params['config_name'] = f'config{node.index}'
147147
return self.template.format(**params)
148+
149+
150+
# Cropping templates
151+
152+
153+
cropping1d_config_template = """struct config{index} : nnet::cropping1d_config {{
154+
static const unsigned in_width = {in_width};
155+
static const unsigned n_chan = {n_chan};
156+
static const unsigned out_width = {out_width};
157+
static const unsigned crop_left = {crop_left};
158+
static const unsigned crop_right = {crop_right};
159+
}};\n"""
160+
161+
cropping2d_config_template = """struct config{index} : nnet::cropping2d_config {{
162+
static const unsigned in_height = {in_height};
163+
static const unsigned in_width = {in_width};
164+
static const unsigned n_chan = {n_chan};
165+
static const unsigned out_height = {out_height};
166+
static const unsigned out_width = {out_width};
167+
static const unsigned crop_top = {crop_top};
168+
static const unsigned crop_bottom = {crop_bottom};
169+
static const unsigned crop_left = {crop_left};
170+
static const unsigned crop_right = {crop_right};
171+
}};\n"""
172+
173+
cropping1d_function_template = 'nnet::cropping1d_{data_format}<{input_t}, {output_t}, {config}>({input}, {output});'
174+
cropping2d_function_template = 'nnet::cropping2d_{data_format}<{input_t}, {output_t}, {config}>({input}, {output});'
175+
176+
cropping_include_list = ['nnet_utils/nnet_cropping.h', 'nnet_utils/nnet_cropping_stream.h']
177+
178+
179+
class CroppingConfigTemplate(LayerConfigTemplate):
180+
def __init__(self):
181+
super().__init__((Cropping1D, Cropping2D))
182+
self.templates = {
183+
'Cropping1D': cropping1d_config_template,
184+
'Cropping2D': cropping2d_config_template,
185+
}
186+
187+
def format(self, node):
188+
params = self._default_config_params(node)
189+
return self.templates[node.class_name].format(**params)
190+
191+
192+
class CroppingFunctionTemplate(FunctionCallTemplate):
193+
def __init__(self):
194+
super().__init__((Cropping1D, Cropping2D), include_header=cropping_include_list)
195+
self.templates = {
196+
'Cropping1D': cropping1d_function_template,
197+
'Cropping2D': cropping2d_function_template,
198+
}
199+
200+
def format(self, node):
201+
params = self._default_function_params(node)
202+
# Cropping1D doesn't have a data_format attribute
203+
params['data_format'] = 'cf' if node.get_attr('data_format') == 'channels_first' else 'cl'
204+
205+
return self.templates[node.class_name].format(**params)

hls4ml/converters/keras/reshaping.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,99 @@ def parse_zeropadding2d_layer(keras_layer, input_names, input_shapes, data_reade
9696
layer['in_width'] = input_shapes[0][2]
9797

9898
return layer, output_shape
99+
100+
101+
@keras_handler('Cropping1D')
102+
def parse_cropping1d_layer(keras_layer, input_names, input_shapes, data_reader):
103+
assert keras_layer['class_name'] == 'Cropping1D'
104+
105+
layer = parse_default_keras_layer(keras_layer, input_names)
106+
107+
cropping = keras_layer['config']['cropping']
108+
if isinstance(cropping, int):
109+
layer['crop_left'] = cropping
110+
layer['crop_right'] = cropping
111+
elif isinstance(cropping, collections.abc.Sequence):
112+
layer['crop_left'] = cropping[0]
113+
layer['crop_right'] = cropping[1]
114+
115+
# No data_format attribute for Cropping1D (always cl), but keeping it consistent with Cropping2D
116+
if layer['data_format'] == 'channels_first':
117+
output_shape = [
118+
input_shapes[0][0], # Batch
119+
input_shapes[0][1], # Channels
120+
input_shapes[0][2] - layer['crop_left'] - layer['crop_right'], # Width
121+
]
122+
layer['out_width'] = output_shape[2]
123+
layer['n_chan'] = output_shape[1]
124+
125+
layer['in_width'] = input_shapes[0][2]
126+
else:
127+
output_shape = [
128+
input_shapes[0][0], # Batch
129+
input_shapes[0][1] - layer['crop_left'] - layer['crop_right'], # Width
130+
input_shapes[0][2], # Channels
131+
]
132+
layer['out_width'] = output_shape[1]
133+
layer['n_chan'] = output_shape[2]
134+
135+
layer['in_width'] = input_shapes[0][1]
136+
137+
return layer, output_shape
138+
139+
140+
@keras_handler('Cropping2D')
141+
def parse_cropping2d_layer(keras_layer, input_names, input_shapes, data_reader):
142+
assert keras_layer['class_name'] == 'Cropping2D'
143+
144+
layer = parse_default_keras_layer(keras_layer, input_names)
145+
146+
cropping = keras_layer['config']['cropping']
147+
if isinstance(cropping, int):
148+
layer['crop_top'] = cropping
149+
layer['crop_bottom'] = cropping
150+
layer['crop_left'] = cropping
151+
layer['crop_right'] = cropping
152+
elif isinstance(cropping, collections.abc.Sequence):
153+
height_crop, width_crop = cropping
154+
if isinstance(height_crop, collections.abc.Sequence):
155+
layer['crop_top'] = height_crop[0]
156+
layer['crop_bottom'] = height_crop[1]
157+
else:
158+
layer['crop_top'] = height_crop
159+
layer['crop_bottom'] = height_crop
160+
if isinstance(width_crop, collections.abc.Sequence):
161+
layer['crop_left'] = width_crop[0]
162+
layer['crop_right'] = width_crop[1]
163+
else:
164+
layer['crop_left'] = width_crop
165+
layer['crop_right'] = width_crop
166+
167+
if layer['data_format'] == 'channels_first':
168+
output_shape = [
169+
input_shapes[0][0], # Batch
170+
input_shapes[0][1], # Channels
171+
input_shapes[0][2] - layer['crop_top'] - layer['crop_bottom'], # Height
172+
input_shapes[0][3] - layer['crop_left'] - layer['crop_right'], # Width
173+
]
174+
layer['out_height'] = output_shape[2]
175+
layer['out_width'] = output_shape[3]
176+
layer['n_chan'] = output_shape[1]
177+
178+
layer['in_height'] = input_shapes[0][2]
179+
layer['in_width'] = input_shapes[0][3]
180+
else:
181+
output_shape = [
182+
input_shapes[0][0], # Batch
183+
input_shapes[0][1] - layer['crop_top'] - layer['crop_bottom'], # Height
184+
input_shapes[0][2] - layer['crop_left'] - layer['crop_right'], # Width
185+
input_shapes[0][3], # Channels
186+
]
187+
layer['out_height'] = output_shape[1]
188+
layer['out_width'] = output_shape[2]
189+
layer['n_chan'] = output_shape[3]
190+
191+
layer['in_height'] = input_shapes[0][1]
192+
layer['in_width'] = input_shapes[0][2]
193+
194+
return layer, output_shape

hls4ml/model/layers.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,47 @@ def initialize(self):
918918
self.add_output_variable(shape, dims, precision=inp.type.precision)
919919

920920

921+
class Cropping1D(Layer):
922+
_expected_attributes = [
923+
Attribute('in_width'),
924+
Attribute('out_width'),
925+
Attribute('n_chan'),
926+
Attribute('crop_left'),
927+
Attribute('crop_right'),
928+
]
929+
930+
def initialize(self):
931+
inp = self.get_input_variable()
932+
# no data_format attribute for Cropping1D
933+
shape = [self.attributes['out_width'], self.attributes['n_chan']]
934+
dims = [f'OUT_WIDTH_{self.index}', f'N_CHAN_{self.index}']
935+
self.add_output_variable(shape, dims, precision=inp.type.precision)
936+
937+
938+
class Cropping2D(Layer):
939+
_expected_attributes = [
940+
Attribute('in_height'),
941+
Attribute('in_width'),
942+
Attribute('out_height'),
943+
Attribute('out_width'),
944+
Attribute('n_chan'),
945+
Attribute('crop_top'),
946+
Attribute('crop_bottom'),
947+
Attribute('crop_left'),
948+
Attribute('crop_right'),
949+
]
950+
951+
def initialize(self):
952+
inp = self.get_input_variable()
953+
if self.get_attr('data_format') == 'channels_last':
954+
shape = [self.attributes['out_height'], self.attributes['out_width'], self.attributes['n_chan']]
955+
dims = [f'OUT_HEIGHT_{self.index}', f'OUT_WIDTH_{self.index}', f'N_CHAN_{self.index}']
956+
else:
957+
shape = [self.attributes['n_chan'], self.attributes['out_height'], self.attributes['out_width']]
958+
dims = [f'N_CHAN_{self.index}', f'OUT_HEIGHT_{self.index}', f'OUT_WIDTH_{self.index}']
959+
self.add_output_variable(shape, dims, precision=inp.type.precision)
960+
961+
921962
class Activation(Layer):
922963
_expected_attributes = [
923964
Attribute('n_in'),
@@ -1752,6 +1793,8 @@ def initialize(self):
17521793
'GlobalAveragePooling2D': GlobalPooling2D,
17531794
'ZeroPadding1D': ZeroPadding1D,
17541795
'ZeroPadding2D': ZeroPadding2D,
1796+
'Cropping1D': Cropping1D,
1797+
'Cropping2D': Cropping2D,
17551798
'Merge': Merge,
17561799
'MatMul': MatMul,
17571800
'Dot': Dot,
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#ifndef NNET_CROPPING_H_
2+
#define NNET_CROPPING_H_
3+
4+
#include <math.h>
5+
6+
namespace nnet {
7+
8+
struct cropping1d_config {
9+
static const unsigned n_chan = 10;
10+
static const unsigned in_width = 10;
11+
static const unsigned out_width = 10;
12+
static const unsigned crop_left = 0;
13+
static const unsigned crop_right = 0;
14+
};
15+
16+
// no need for channel first for 1D cropping (no keras equivalent)
17+
template <class data_T, class res_T, typename CONFIG_T>
18+
void cropping1d_cl(data_T data[CONFIG_T::n_chan * CONFIG_T::in_width], res_T res[CONFIG_T::n_chan * CONFIG_T::out_width]) {
19+
#pragma HLS PIPELINE
20+
21+
// Skip cropped input from left
22+
data += CONFIG_T::crop_left * CONFIG_T::n_chan;
23+
24+
// Fill upto out_width (implicit cropping from right)
25+
for (int i = 0; i < CONFIG_T::out_width; i++) {
26+
for (int j = 0; j < CONFIG_T::n_chan; j++) {
27+
*(res++) = (res_T) * (data++);
28+
}
29+
}
30+
}
31+
32+
struct cropping2d_config {
33+
static const unsigned n_chan = 10;
34+
static const unsigned in_height = 10;
35+
static const unsigned in_width = 10;
36+
static const unsigned out_height = 10;
37+
static const unsigned out_width = 10;
38+
static const unsigned crop_top = 0;
39+
static const unsigned crop_bottom = 0;
40+
static const unsigned crop_left = 0;
41+
static const unsigned crop_right = 0;
42+
};
43+
44+
template <class data_T, class res_T, typename CONFIG_T>
45+
void cropping2d_cf(data_T data[CONFIG_T::n_chan * CONFIG_T::in_height * CONFIG_T::in_width],
46+
res_T res[CONFIG_T::n_chan * CONFIG_T::out_height * CONFIG_T::out_width]) {
47+
#pragma HLS PIPELINE
48+
49+
for (int k = 0; k < CONFIG_T::n_chan; k++) { // channels first
50+
// Skip current channel data from top and left
51+
data_T *data_ptr = data + k * CONFIG_T::in_height * CONFIG_T::in_width + CONFIG_T::crop_top * CONFIG_T::in_width +
52+
CONFIG_T::crop_left;
53+
54+
// Fill upto out_height and out_width
55+
for (int i = 0; i < CONFIG_T::out_height; i++) {
56+
data_T *row_ptr = data_ptr + i * CONFIG_T::in_width;
57+
for (int j = 0; j < CONFIG_T::out_width; j++) {
58+
*(res++) = (res_T) * (row_ptr++);
59+
}
60+
}
61+
}
62+
}
63+
64+
template <class data_T, class res_T, typename CONFIG_T>
65+
void cropping2d_cl(data_T data[CONFIG_T::n_chan * CONFIG_T::in_height * CONFIG_T::in_width],
66+
res_T res[CONFIG_T::n_chan * CONFIG_T::out_height * CONFIG_T::out_width]) {
67+
#pragma HLS PIPELINE
68+
69+
for (int i = 0; i < CONFIG_T::out_height; i++) {
70+
int in_row = i + CONFIG_T::crop_top;
71+
for (int j = 0; j < CONFIG_T::out_width; j++) {
72+
int in_col = j + CONFIG_T::crop_left;
73+
74+
data_T *data_ptr = data + (in_row * CONFIG_T::in_width + in_col) * CONFIG_T::n_chan;
75+
for (int k = 0; k < CONFIG_T::n_chan; k++) { // channels last
76+
*(res++) = (res_T) * (data_ptr++);
77+
}
78+
}
79+
}
80+
}
81+
82+
} // namespace nnet
83+
84+
#endif
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#ifndef NNET_CROPPING_STREAM_H_
2+
#define NNET_CROPPING_STREAM_H_
3+
4+
#include "nnet_padding_stream.h" // fill_data function
5+
#include <math.h>
6+
7+
namespace nnet {
8+
9+
template <class data_T, class res_T, typename CONFIG_T>
10+
void cropping1d_cl(hls::stream<data_T> &data, hls::stream<res_T> &res) {
11+
#pragma HLS PIPELINE
12+
13+
// Discard left
14+
for (int i = 0; i < CONFIG_T::crop_left; i++) {
15+
data.read();
16+
}
17+
18+
for (int i = 0; i < CONFIG_T::out_width; i++) {
19+
fill_data<data_T, res_T, CONFIG_T>(data, res);
20+
}
21+
22+
// Discard right
23+
for (int i = 0; i < CONFIG_T::crop_right; i++) {
24+
data.read();
25+
}
26+
}
27+
28+
template <class data_T, class res_T, typename CONFIG_T>
29+
void cropping2d_cl(hls::stream<data_T> &data, hls::stream<res_T> &res) {
30+
#pragma HLS PIPELINE
31+
32+
// Discard top rows
33+
for (int i = 0; i < CONFIG_T::crop_top; i++) {
34+
for (int j = 0; j < CONFIG_T::in_width; j++) {
35+
data.read();
36+
}
37+
}
38+
39+
for (int i = 0; i < CONFIG_T::out_height; i++) {
40+
// Discard left columns
41+
for (int j = 0; j < CONFIG_T::crop_left; j++) {
42+
data.read();
43+
}
44+
45+
for (int j = 0; j < CONFIG_T::out_width; j++) {
46+
fill_data<data_T, res_T, CONFIG_T>(data, res);
47+
}
48+
49+
// Discard right columns
50+
for (int j = 0; j < CONFIG_T::crop_right; j++) {
51+
data.read();
52+
}
53+
}
54+
55+
// Discard bottom rows
56+
for (int i = 0; i < CONFIG_T::crop_bottom; i++) {
57+
for (int j = 0; j < CONFIG_T::in_width; j++) {
58+
data.read();
59+
}
60+
}
61+
}
62+
63+
} // namespace nnet
64+
65+
#endif

0 commit comments

Comments
 (0)