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 |