GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 92.1% 58 / 0 / 63
Functions: 100.0% 3 / 0 / 3
Branches: 35.9% 28 / 0 / 78

src/cpu/image_preproc.cpp
Line Branch Exec Source
1 // ─── CPU image preprocessing helpers ───────────────────────────────────────
2 //
3 // Two ops shared by vision models (CLIP, SAM, Depth Anything, DETR, …):
4 //
5 // image_normalize — per-channel (X - mean[c]) / std[c] on NCHW.
6 // The ImageNet / CLIP / SAM preprocess step,
7 // and the inverse of "renormalise to model
8 // distribution" in any decoder head.
9 //
10 // image_u8_to_f32_nhwc_to_nchw — convert a packed uint8 HWC image buffer
11 // (e.g. straight from a JPEG decoder) into a
12 // FP32 NCHW tensor, applying a single
13 // scale+bias pass: Y = src * scale + bias.
14 // Covers the typical scaling conventions:
15 // [0,255] -> [0,1] : scale=1/255, bias=0
16 // [0,255] -> [-1,1] : scale=2/255, bias=-1
17 //
18 // Both are FP32-only on CPU (matches the rest of the backend).
19 //
20 // `image_u8_to_f32_nhwc_to_nchw` takes a raw `const uint8_t*` host pointer —
21 // pixel data essentially always originates host-side from an image decoder,
22 // and forcing a Tensor wrapper around uint8 bytes would require either a
23 // new UINT8 dtype or misusing INT8 (signed range). The op-table signature
24 // matches `embedding_lookup_forward(const int32_t*)` in spirit.
25
26 #include <brotensor/tensor.h>
27
28 #include <cmath>
29 #include <cstdint>
30 #include <stdexcept>
31 #include <string>
32
33 namespace brotensor::detail::cpu {
34
35 namespace {
36
37 12 inline void check_fp32(const ::brotensor::Tensor& t,
38 const char* op, const char* name) {
39
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (t.dtype != Dtype::FP32) {
40 throw std::runtime_error(std::string(op) + ": " + name +
41 " must be FP32 (CPU backend is FP32-only)");
42 }
43 12 }
44
45 } // namespace
46
47 4 void image_normalize(const ::brotensor::Tensor& X,
48 const ::brotensor::Tensor& mean,
49 const ::brotensor::Tensor& std_,
50 int N, int C, int H, int W,
51 ::brotensor::Tensor& Y) {
52 4 check_fp32(X, "image_normalize", "X");
53 4 check_fp32(mean, "image_normalize", "mean");
54 4 check_fp32(std_, "image_normalize", "std");
55
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (mean.size() != C || std_.size() != C) {
56 throw std::runtime_error("image_normalize: mean/std must have C elements");
57 }
58 4 const int spatial = H * W;
59 4 const int cols = C * spatial;
60
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (X.rows != N || X.cols != cols) {
61 throw std::runtime_error("image_normalize: X shape mismatch");
62 }
63
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
4 if (Y.rows != N || Y.cols != cols || Y.dtype != Dtype::FP32) {
64 4 Y.resize(N, cols, Dtype::FP32);
65 4 }
66
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (N == 0 || cols == 0) return;
67
68 4 const float* Xp = X.host_f32();
69 4 const float* mp = mean.host_f32();
70 4 const float* sp = std_.host_f32();
71 4 float* Yp = Y.host_f32_mut();
72
73
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12 times.
15 for (int c = 0; c < C; ++c) {
74 12 const float mu = mp[c];
75 12 const float s = sp[c];
76
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 1 time.
12 if (s == 0.0f) {
77
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 throw std::runtime_error("image_normalize: std[c] == 0");
78 }
79 11 const float inv = 1.0f / s;
80
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 11 times.
28 for (int n = 0; n < N; ++n) {
81 17 const float* x_chan = Xp + (n * C + c) * spatial;
82 17 float* y_chan = Yp + (n * C + c) * spatial;
83
2/2
✓ Branch 0 taken 596 times.
✓ Branch 1 taken 17 times.
613 for (int s_idx = 0; s_idx < spatial; ++s_idx) {
84 596 y_chan[s_idx] = (x_chan[s_idx] - mu) * inv;
85 596 }
86 17 }
87 11 }
88 3 }
89
90 6 void image_u8_to_f32_nhwc_to_nchw(const uint8_t* src,
91 int N, int H, int W, int C,
92 float scale, float bias,
93 ::brotensor::Tensor& Y) {
94
1/10
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
6 if (src == nullptr && N > 0 && H > 0 && W > 0 && C > 0) {
95 throw std::runtime_error("image_u8_to_f32_nhwc_to_nchw: src is null");
96 }
97
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (N < 0 || H < 0 || W < 0 || C < 0) {
98 throw std::runtime_error("image_u8_to_f32_nhwc_to_nchw: negative dim");
99 }
100 6 const int spatial = H * W;
101 6 const int cols = C * spatial;
102
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 if (Y.rows != N || Y.cols != cols || Y.dtype != Dtype::FP32) {
103 6 Y.resize(N, cols, Dtype::FP32);
104 6 }
105
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (N == 0 || cols == 0) return;
106
107 6 float* Yp = Y.host_f32_mut();
108
109 // src indexed (n, h, w, c)_packed; Y indexed (n, c, h, w)_NCHW.
110
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
16 for (int n = 0; n < N; ++n) {
111 10 const uint8_t* src_n = src + n * spatial * C;
112 10 float* y_n = Yp + n * C * spatial;
113
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 10 times.
32 for (int c = 0; c < C; ++c) {
114 22 float* y_chan = y_n + c * spatial;
115
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 22 times.
92 for (int h = 0; h < H; ++h) {
116
2/2
✓ Branch 0 taken 358 times.
✓ Branch 1 taken 70 times.
428 for (int w = 0; w < W; ++w) {
117 358 const uint8_t v = src_n[(h * W + w) * C + c];
118 358 y_chan[h * W + w] = static_cast<float>(v) * scale + bias;
119 358 }
120 70 }
121 22 }
122 10 }
123 6 }
124
125 } // namespace brotensor::detail::cpu
126