Skip to content

Commit

Permalink
Add Integral image (#3)
Browse files Browse the repository at this point in the history
* Add preliminary version of integral image

* Add missing imports

* point to local zignal dependency

* Fix integral image computation and at tests

* simplify isStruct
  • Loading branch information
arrufat authored May 10, 2024
1 parent 2ead875 commit 89618bf
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 3 deletions.
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run library tests");
for ([_][]const u8{
"color",
"image",
"geometry",
"matrix",
"svd",
Expand Down
3 changes: 1 addition & 2 deletions examples/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

.dependencies = .{
.zignal = .{
.url = "https://github.com/bfactory-ai/zignal/archive/c784809c4c0eb914694998b5434c25d9efd379b9.tar.gz",
.hash = "1220a6447aee50bedc4ecea70a562ef2833267011878db344661627485cdce756b80",
.path = "../",
},
},

Expand Down
97 changes: 96 additions & 1 deletion src/image.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const Allocator = std.mem.Allocator;
const Rgba = @import("color.zig").Rgba;
const as = @import("meta.zig").as;
const isScalar = @import("meta.zig").isScalar;
const isStruct = @import("meta.zig").isStruct;
const Rectangle = @import("geometry.zig").Rectangle(f32);
const Point2d = @import("point.zig").Point2d(f32);

Expand Down Expand Up @@ -109,7 +112,6 @@ pub fn Image(comptime T: type) type {
}
}

/// Rotates the image by angle (in radians) from the given center. It must be freed on the caller side.
pub fn rotateFrom(self: Self, allocator: Allocator, center: Point2d, angle: f32, rotated: *Self) !void {
var array = std.ArrayList(T).init(allocator);
try array.resize(self.rows * self.cols);
Expand Down Expand Up @@ -151,5 +153,98 @@ pub fn Image(comptime T: type) type {
}
}
}

/// Computes the integral image of self.
pub fn integralImage(
self: Self,
allocator: Allocator,
integral: *Image(if (isScalar(T)) f32 else if (isStruct(T)) [std.meta.fields(T).len]f32 else @compileError("Can't compute the integral image of " ++ @typeName(T) ++ ".")),
) !void {
switch (@typeInfo(T)) {
.ComptimeInt, .Int, .ComptimeFloat, .Float => {
integral.* = try Image(f32).initAlloc(allocator, self.rows, self.cols);
var tmp: f32 = 0;
for (0..self.cols) |c| {
tmp += as(f32, (self.data[c]));
integral.data[c] = tmp;
}
for (1..self.rows) |r| {
tmp = 0;
for (0..self.cols) |c| {
const curr_pos = r * self.cols + c;
const prev_pos = (r - 1) * self.cols + c;
tmp += as(f32, self.data[curr_pos]);
integral.data[curr_pos] = tmp + integral.data[prev_pos];
}
}
},
.Struct => {
const num_fields = std.meta.fields(T).len;
integral.* = try Image([num_fields]f32).initAlloc(allocator, self.rows, self.cols);
var tmp = [_]f32{0} ** num_fields;
for (0..self.cols) |c| {
inline for (std.meta.fields(T), 0..) |f, i| {
if (c < 1) {
std.log.debug("{s}: {d}", .{ f.name, @field(self.data[c], f.name) });
}
tmp[i] += as(f32, @field(self.data[c], f.name));
integral.data[c][i] = tmp[i];
}
}
for (1..self.rows) |r| {
tmp = [_]f32{0} ** num_fields;
for (0..self.cols) |c| {
const curr_pos = r * self.cols + c;
const prev_pos = (r - 1) * self.cols + c;
inline for (std.meta.fields(T), 0..) |f, i| {
tmp[i] += as(f32, @field(self.data[curr_pos], f.name));
integral.data[curr_pos][i] = tmp[i] + integral.data[prev_pos][i];
}
}
}
},
else => @compileError("Can't compute the integral image of " ++ @typeName(T) ++ "."),
}
}
};
}

test "integral image scalar" {
var image = try Image(u8).initAlloc(std.testing.allocator, 21, 13);
defer image.deinit(std.testing.allocator);
for (image.data) |*i| i.* = 1;
var integral: Image(f32) = undefined;
try image.integralImage(std.testing.allocator, &integral);
defer integral.deinit(std.testing.allocator);
try expectEqual(image.rows, integral.rows);
try expectEqual(image.cols, integral.cols);
try expectEqual(image.data.len, integral.data.len);
for (0..image.rows) |r| {
for (0..image.cols) |c| {
const pos = r * image.cols + c;
const area_at_pos: f32 = @floatFromInt((r + 1) * (c + 1));
try expectEqual(area_at_pos, integral.data[pos]);
}
}
}

test "integral image struct" {
var image = try Image(Rgba).initAlloc(std.testing.allocator, 21, 13);
defer image.deinit(std.testing.allocator);
for (image.data) |*i| i.* = .{ .r = 1, .g = 1, .b = 1, .a = 1 };
var integral: Image([4]f32) = undefined;
try image.integralImage(std.testing.allocator, &integral);
defer integral.deinit(std.testing.allocator);
try expectEqual(image.rows, integral.rows);
try expectEqual(image.cols, integral.cols);
try expectEqual(image.data.len, integral.data.len);
for (0..image.rows) |r| {
for (0..image.cols) |c| {
const pos = r * image.cols + c;
const area_at_pos: f32 = @floatFromInt((r + 1) * (c + 1));
for (0..4) |i| {
try expectEqual(area_at_pos, integral.data[pos][i]);
}
}
}
}
13 changes: 13 additions & 0 deletions src/meta.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,16 @@ pub inline fn as(comptime T: type, from: anytype) T {
else => @compileError(@typeName(@TypeOf(from) ++ " is not supported.")),
}
}

/// Returns true if and only if T represents a scalar type.
pub inline fn isScalar(comptime T: type) bool {
return switch (@typeInfo(T)) {
.ComptimeInt, .Int, .ComptimeFloat, .Float => true,
else => false,
};
}

/// Returns true if and only if T represents a struct type.
pub inline fn isStruct(comptime T: type) bool {
return @typeInfo(T) == .Struct;
}

0 comments on commit 89618bf

Please sign in to comment.