1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
| ## -*- mode: tcl ; fill-column: 90 -*-
# # ## ### ##### ######## ############# #####################
## Transformers -- Histogram Statistics
## Not handled in the by-* specs. Histogram statistics are sufficiently different in the
## details of their calculation from the others (aktive_histogram* client data, etc.) to
## warrant separate handling. Therefore sliced on the operation instead.
#
## NOTES
##
## - Caches are used to hold full row|column histograms. While this requires more memory
## we get higher speed as no histogram is computed more than once. Should we find that
## memory usage is too high we can
## (1) limit the cache size, at the at the cost of having to re-compute some histograms.
## (2) spill to and reload from disk. Beware that this may be slower than a re-compute.
##
## - For band and tile histograms caches are not needed, as both return per-pixel results
## and automatically do not re-compute anything.
#
## This is a place where future management of request types (i.e. by rows, by columns, by
## tiles, etc.) could be useful.
# # ## ### ##### ######## ############# #####################
## Semi-compress bands/rows/columns/tiles/image down to a statistic (histogram).
operator op::image::histogram {
section transform statistics
example {
aktive op sdf 2image smooth [aktive op sdf ring [aktive image sdf triangle width 128 height 128 a {10 10} b {50 80} c {80 30}] thickness 4]
@1 bins 8 | -matrix -int
}
int? 256 bins \
The number of bins in the returned histogram. The pixel values are quantized \
to fit. Only values in the range of \[0..1\] are considered valid. Values \
outside of that range are placed into the smallest/largest bin. \
\
The default quantizes the image values to 8-bit.
input
note Returns image with the input transformed into a histogram of `bins` values.
# Deprecated: Note: it is computed as the column sum of the row histograms of the input
#
# Note: it is computed as the transposed row sum of the column histograms of the input
#
# in the current setup, where we concurrently scan over rows
# the column sum of the original implementation returns a single row,
# causing threading to be effectively disabled.
#
# while the new form computes column histograms their vector cache ensures
# that each is calculated only once, and the row sums are concurrent.
#
# TODO FUTURE - ops declare prefered access pattern, sinks choose
# TODO FUTURE - sink auto-chooses different patterns based on image geometry
# i.e. thin tall -> by rows, thin wide -> by columns, else ops preference
body {
if 0 {
set src [aktive op row histogram $src bins $bins]
set src [aktive op column sum $src]
} else {
set src [aktive op column histogram $src bins $bins]
set src [aktive op row sum $src]
set src [aktive op transpose $src]
}
return $src
}
}
operator {dim unchanged} {
op::band::histogram band {width and height}
op::tile::histogram {} {}
} {
op -> _ kind _
section transform statistics
if {$kind eq "tile"} {
uint radius Tile size as radius from center. \
Full width and height of the tile are `2*radius+1`.
}
int? 256 bins \
The number of bins held by a single histogram. The pixel values are quantized \
to fit. Only values in the range of \[0..1\] are considered valid. Values \
outside of that range are placed into the smallest/largest bin. \
\
The default quantizes the image values to 8-bit.
input
note Returns image with input ${kind}s transformed into a histogram of `bins` values.
switch -exact -- $kind {
band {
note The result is an image of bin-sized histogram ${dim}s with {*}$unchanged of the input
}
tile {
note Only single-band images are legal inputs. The result will have `bins` bands.
note Beware, the operator consumes overlapping tiles, not just adjacent.
note Beware, the result image is shrunken by 2*radius in width and height \
relative to the input. Inputs smaller than that are rejected.
note If shrinkage is not desired add a border to the input using one of \
the `aktive op embed ...` operators before applying this operator.
note The prefered embedding for histogram is black. \
It is chosen to have minimal to no impact on the statistics \
at the original input's borders.
}
}
def fieldsetup [dict get {
band { state->size = domain->depth; domain->depth = param->bins; }
tile {
if (domain->depth > 1) {
aktive_failf ("rejecting input with depth %d != 1", domain->depth);
}
aktive_uint len = 2*param->radius+1;
if ((domain->width < len) ||
(domain->height < len)) {
aktive_failf ("Image %dx%d too small for %d-radius tiles",
domain->width, domain->height, param->radius);
}
state->size = len*len;
domain->x += param->radius;
domain->y += param->radius;
domain->width -= 2*param->radius;
domain->height -= 2*param->radius;
domain->depth = param->bins;
}
} $kind]
state -fields {
aktive_uint size; // quick access to the original size of the reduced @@kind@@s
} -setup {
aktive_geometry_copy (domain, aktive_image_get_geometry (srcs->v[0]));
@@fieldsetup@@
}
switch -exact -- $kind {
band {
blit histogrammer {
{AH {y AY 1 up} {y 0 1 up}}
{AW {x AX 1 up} {x 0 1 up}}
} {raw histogram-band {
// dstvalue = row/col start - 1-strided band (full histogram)
// srcvalue = row/col start - 1-strided full band vector
aktive_reduce_histogram (srcvalue, SD, 1, &state->h);
memcpy (dstvalue, state->h.count, DD * sizeof(double));
}}
}
tile {
# This assumes a single band input
blit histogrammer {
{AH {y AY 1 up} {y radius 1 up}}
{AW {x AX 1 up} {x radius 1 up}}
} {raw histogram-tile {
// dstvalue = cells to set (bands = histogram)
// srcvalue = center cell of tile
aktive_tile_reduce_histogram (srcvalue, radius, srcpos, SRCCAP, srcpitch, srcstride, &state->h);
memcpy (dstvalue, state->h.count, DD * sizeof(double));
}}
}
}
def subrequest [dict get {
band {}
tile {
aktive_uint radius = param->radius;
subrequest.x -= radius;
subrequest.y -= radius;
subrequest.width += 2 * radius;
subrequest.height += 2 * radius;
}
} $kind]
pixels -state {
aktive_histogram h;
} -setup {
state->h.bins = param->bins;
state->h.maxbin = param->bins - 1;
state->h.count = NALLOC (double, param->bins);
} -cleanup {
ckfree (state->h.count);
} {
TRACE("histogram actual %u bins", param->bins);
aktive_rectangle_def_as (subrequest, request);
@@subrequest@@
TRACE_RECTANGLE_M("@@kind@@ hist", &subrequest);
aktive_block* src = aktive_region_fetch_area (srcs->v[0], &subrequest);
@@histogrammer@@
TRACE_DO (__aktive_block_dump ("@@kind@@ histogram out", block));
}
}
# # ## ### ##### ######## ############# #####################
operator oaxis {
op::row::histogram height
op::column::histogram width
} {
op -> _ kind _
example {
aktive op sdf 2image smooth [aktive op sdf ring [aktive image sdf triangle width 32 height 32 a {10 10} b {30 80} c {80 30}] thickness 4]
@1 bins 8 | -matrix -int
}
section transform statistics
int? 256 bins \
The number of bins held by a single histogram. The pixel values are quantized \
to fit. Only values in the range of \[0..1\] are considered valid. Values \
outside of that range are placed into the smallest/largest bin. \
\
The default quantizes the image values to 8-bit.
note Returns image with input ${kind}s transformed into a histogram of `bins` values.
note The result is an image of `bins`-sized histogram ${kind}s with $oaxis and depth of the input.
# !xref-mark histogram
cached $kind histogram AKTIVE_HISTOGRAM_FILL -fields {
aktive_histogram h;
} -setup {
state->h.bins = param->bins;
state->h.maxbin = param->bins - 1;
state->h.count = NALLOC (double, param->bins);
TRACE("histogram actual %u bins", param->bins);
} -cleanup {
ckfree (state->h.count);
} -rsize "param->bins" -cdata "&state->h"
# !xref-mark /end
}
##
# # ## ### ##### ######## ############# #####################
::return
|