## -*- 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