Skip to content

Commit 5724f95

Browse files
authored
Merge pull request #230 from ClojureCivitas/tensor-images-4
tensor image wip
2 parents 545348d + 3a8d4f9 commit 5724f95

1 file changed

Lines changed: 51 additions & 59 deletions

File tree

src/dtype_next/image_processing_with_tensors.clj

Lines changed: 51 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,11 @@ sample-buffer
282282
(def sample-reader
283283
(dtype/->reader sample-buffer))
284284

285-
;; Access elements by index:
285+
;; **Important**: Readers act as functions of their index. You can call them directly
286+
;; instead of using `dtype/get-value`. This pattern extends to tensors as well—both
287+
;; readers and tensors are callable:
286288

287-
(dtype/get-value sample-reader 0)
289+
(sample-reader 0)
288290

289291
;; Map over readers like sequences:
290292

@@ -378,11 +380,15 @@ writable-buffer
378380

379381
;; **Key insight**: All these are zero-copy views—no data is copied.
380382

381-
;; ## Element Access: tensor/mget
383+
;; ## Element Access: Tensors as Functions
382384

383-
;; `tensor/mget` reads a single element at given indices:
385+
;; Like readers, tensors act as functions of their indices. You can call them directly
386+
;; with coordinate arguments:
384387

385-
(tensor/mget toy-tensor 1 2)
388+
(toy-tensor 1 2)
389+
390+
;; This is equivalent to `(tensor/mget toy-tensor 1 2)` but more idiomatic. Both readers
391+
;; and tensors follow this pattern—they're callable values, not just data structures.
386392

387393
;; ## Slicing Dimensions: tensor/slice and tensor/slice-right
388394

@@ -544,71 +550,49 @@ flat-tensor
544550

545551
;; ## Extracting Color Channels
546552

547-
;; There are two main approaches for extracting channels:
548-
;;
549-
;; 1. **tensor/select** — Extract specific channels by index
550-
;; 2. **tensor/slice-right** — Iterate over all channels cleanly
551-
;;
552-
;; We'll use `tensor/select` for explicit channel extraction:
553+
;; Use `tensor/slice-right` to extract all channels cleanly:
553554

554555
(def channels
555556
(let [[blue green red] (tensor/slice-right original-tensor 1)]
556557
{:blue blue :green green :red red}))
557558

558559
;; Each channel is now `[H W]` instead of `[H W C]`:
559560

560-
(:red channels)
561+
(dtype/shape (:red channels))
561562

562563
;; **Key insight**: These are **zero-copy views** into the original tensor—no data is copied.
563564
;;
564-
;; **Alternative with tensor/select**:
565-
;; Blue channel:
566-
(tensor/select original-tensor :all :all 2)
565+
;; **Alternative with tensor/select** (when you need one specific channel):
566+
(def blue-only (tensor/select original-tensor :all :all 0)) ; Channel 0 = Blue
567+
568+
(dtype/shape blue-only)
567569

568570
;; ## Channel Statistics
569571

570-
;; Compute mean, standard deviation, min, max for each channel:
572+
;; Compute mean, standard deviation, min, max, and percentiles for each channel:
571573

572574
(defn channel-stats
573575
"Compute statistics for a single channel tensor.
574576
Takes: [H W] tensor
575-
Returns: map with :mean, :std, :min, :max scalars"
577+
Returns: map with :mean, :std, :min, :max, percentiles"
576578
[channel]
577-
{:mean (dfn/mean channel)
578-
:std (dfn/standard-deviation channel)
579-
:min (dfn/reduce-min channel)
580-
:max (dfn/reduce-max channel)})
579+
(let [percentiles (dfn/percentiles channel [25 50 75])]
580+
{:mean (dfn/mean channel)
581+
:std (dfn/standard-deviation channel)
582+
:min (dfn/reduce-min channel)
583+
:max (dfn/reduce-max channel)
584+
:q25 (percentiles 0)
585+
:median (percentiles 1)
586+
:q75 (percentiles 2)}))
587+
588+
;; Apply to our extracted channels:
581589

582590
(->> channels
583591
(map (fn [[k v]]
584592
(merge {:channel k}
585593
(channel-stats v))))
586594
tc/dataset)
587595

588-
;; ## Enhanced Channel Statistics with slice-right
589-
590-
;; Now let's see a cleaner approach using `tensor/slice-right` with more comprehensive
591-
;; statistics including percentiles:
592-
593-
(tc/dataset
594-
(map (fn [color channel]
595-
(let [percentiles (dfn/percentiles channel [25 50 75])]
596-
{:channel color
597-
:mean (dfn/mean channel)
598-
:std (dfn/standard-deviation channel)
599-
:min (dfn/reduce-min channel)
600-
:max (dfn/reduce-max channel)
601-
:q25 (dtype/get-value percentiles 0)
602-
:median (dtype/get-value percentiles 1)
603-
:q75 (dtype/get-value percentiles 2)}))
604-
["blue" "green" "red"]
605-
(tensor/slice-right original-tensor 1)))
606-
607-
;; **Key advantages of slice-right**:
608-
;; - Cleaner iteration over all channels
609-
;; - No need to manually specify indices
610-
;; - Natural destructuring: `(let [[b g r] (tensor/slice-right img 1)] ...)`
611-
612596
;; ## Brightness Analysis
613597

614598
;; Convert to grayscale using perceptual luminance formula.
@@ -710,6 +694,14 @@ flat-tensor
710694
{:pair "Blue-Red" :correlation (correlation blue red)}
711695
{:pair "Green-Red" :correlation (correlation green red)}]))
712696

697+
(let [[blue green red] (tensor/slice-right (auto-white-balance
698+
original-tensor)
699+
1)]
700+
(tc/dataset
701+
[{:pair "Blue-Green" :correlation (correlation blue green)}
702+
{:pair "Blue-Red" :correlation (correlation blue red)}
703+
{:pair "Green-Red" :correlation (correlation green red)}]))
704+
713705
;; **Interpretation**:
714706
;; - Correlation near 1.0: Channels move together (consistent lighting, color cast)
715707
;; - Correlation near 0.0: Channels independent (varied colors, good white balance)
@@ -955,14 +947,14 @@ edges
955947
[tensor-2d]
956948
(let [flat (dtype/as-reader (tensor/reshape tensor-2d [(dtype/ecount tensor-2d)]))
957949
[h w] (dtype/shape tensor-2d)
958-
max-idx (apply max-key #(dtype/get-value flat %) (range (dtype/ecount flat)))
959-
min-idx (apply min-key #(dtype/get-value flat %) (range (dtype/ecount flat)))]
950+
max-idx (apply max-key #(flat %) (range (dtype/ecount flat)))
951+
min-idx (apply min-key #(flat %) (range (dtype/ecount flat)))]
960952
{:brightest {:block-y (quot max-idx w)
961953
:block-x (rem max-idx w)
962-
:value (dtype/get-value flat max-idx)}
954+
:value (flat max-idx)}
963955
:darkest {:block-y (quot min-idx w)
964956
:block-x (rem min-idx w)
965-
:value (dtype/get-value flat min-idx)}}))
957+
:value (flat min-idx)}}))
966958

967959
(find-block-extremes brightness-map)
968960

@@ -1006,7 +998,7 @@ edges
1006998
;; Scale each channel (vectorized operations per channel)
1007999
scaled-channels (mapv (fn [ch]
10081000
(let [channel (tensor/select img-tensor :all :all ch)
1009-
scale (dtype/get-value scale-factors ch)]
1001+
scale (scale-factors ch)]
10101002
(dtype/elemwise-cast
10111003
(dfn/min 255 (dfn/* channel scale))
10121004
:uint8)))
@@ -1016,7 +1008,7 @@ edges
10161008
(tensor/compute-tensor
10171009
[h w c]
10181010
(fn [y x ch]
1019-
(tensor/mget (nth scaled-channels ch) y x))
1011+
((nth scaled-channels ch) y x))
10201012
:uint8)))
10211013

10221014
(kind/table
@@ -1059,7 +1051,7 @@ edges
10591051
(tensor/compute-tensor
10601052
[h w c]
10611053
(fn [y x ch]
1062-
(tensor/mget (nth enhanced-channels ch) y x))
1054+
((nth enhanced-channels ch) y x))
10631055
:uint8)))
10641056

10651057
(def contrasted (enhance-contrast original-tensor 1.5))
@@ -1149,9 +1141,9 @@ edges
11491141
[h w 3]
11501142
(fn [y x c]
11511143
(case c
1152-
0 (tensor/mget new-b-clamped y x) ; Blue channel 0
1153-
1 (tensor/mget new-g-clamped y x) ; Green channel 1
1154-
2 (tensor/mget new-r-clamped y x))) ; Red channel 2
1144+
0 (new-b-clamped y x) ; Blue channel 0
1145+
1 (new-g-clamped y x) ; Green channel 1
1146+
2 (new-r-clamped y x))) ; Red channel 2
11551147
:uint8)))
11561148

11571149
(defn simulate-color-blindness
@@ -1248,8 +1240,8 @@ kernel-3x3
12481240
in-bounds? (and (>= img-y 0) (< img-y h)
12491241
(>= img-x 0) (< img-x w))
12501242
new-sum (if in-bounds?
1251-
(+ sum (* (tensor/mget kernel ky kx)
1252-
(tensor/mget img-2d img-y img-x)))
1243+
(+ sum (* (kernel ky kx)
1244+
(img-2d img-y img-x)))
12531245
sum)
12541246
[next-ky next-kx] (if (>= (inc kx) kw)
12551247
[(inc ky) 0]
@@ -1573,12 +1565,12 @@ gaussian-5x5
15731565
;; - `dtype/elemwise-cast` — Convert between types
15741566
;; - `dtype/ecount` — Total element count
15751567
;; - `dtype/as-reader` — Convert to readable sequence
1576-
;; - `dtype/get-value` — Extract scalar value
1568+
;; - Readers act as functions: `(reader idx)` instead of `(dtype/get-value reader idx)`
15771569

15781570
;; **tensor namespace (tech.v3.tensor):**
15791571
;; - `tensor/compute-tensor` — Functionally construct tensors
15801572
;; - `tensor/select` — Extract slices, channels (zero-copy)
1581-
;; - `tensor/mget` — Read single elements
1573+
;; - Tensors act as functions: `(tensor y x)` instead of `(tensor/mget tensor y x)`
15821574
;; - `tensor/reshape` — Reinterpret tensor shape (zero-copy)
15831575
;; - `tensor/reduce-axis` — Reduce along specific dimension
15841576

0 commit comments

Comments
 (0)