Skip to content

Commit cc3b17e

Browse files
authored
Merge pull request #221 from ClojureCivitas/geotiff-with-dtype-next
Geotiff with dtype next
2 parents 0eb0897 + a07dd8b commit cc3b17e

File tree

1 file changed

+70
-52
lines changed

1 file changed

+70
-52
lines changed

src/gis/geotiff.clj

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@
1010

1111
(ns gis.geotiff
1212
(:require
13-
[scicloj.kindly.v4.kind :as kind]))
13+
[scicloj.kindly.v4.kind :as kind]
14+
[tech.v3.datatype :as dtype]
15+
[tech.v3.tensor :as tensor]
16+
[tech.v3.tensor.dimensions :as dims]
17+
[tech.v3.libs.buffered-image :as bufimg]
18+
[tablecloth.api :as tc]
19+
[clojure.java.io :as io]
20+
[tech.v3.dataset.tensor :as ds-tensor]))
1421
;; # Cloud Optimized GeoTIFFs in JVM Clojure
1522
;; ## Motivation
1623
;; This document shows several different methods for handling COGs in JVM Clojure without
@@ -33,6 +40,8 @@
3340
[:img {:src "resources/tiff_geotiff_cog.png"
3441
:style {:width "60%"}}])
3542

43+
44+
3645
;; More about the internal structure of COGs can be learned [in the Cloud Native Geo Guide](https://guide.cloudnativegeo.org/cloud-optimized-geotiffs/intro.html) and at [COGeo](https://cogeo.org/).
3746

3847
;; # TIFF handling with built-in Java libraries
@@ -72,14 +81,17 @@
7281
{:images images
7382
:reader-format (.getFormatName reader)}))))
7483

84+
(require '[tech.v3.datatype :as dtype])
85+
7586
(defn get-pixel [^BufferedImage image x y]
7687
(let [raster (.getRaster image)
7788
;; Handle images with an arbitrary number of bands.
7889
;; Remote sensing imagery is often `multispectral`.
7990
num-bands (.getNumBands raster)
8091
pixel (double-array num-bands)]
8192
(.getPixel raster x y pixel)
82-
(vec pixel)))
93+
;; A dtype-next read-only buffer as a view of the Java array:
94+
(dtype/as-reader pixel)))
8395

8496
;; Now we use the above helper functions to handle our locally held TIFF.
8597

@@ -126,31 +138,51 @@
126138
;; For our purposes we can just add the tablecloth dependency and that will transitively
127139
;; bring along the rest of the stack.
128140

129-
(require '[tablecloth.api :as tc])
141+
(require '[tablecloth.api :as tc]
142+
'[tech.v3.tensor :as tensor]
143+
'[tech.v3.dataset.tensor :as ds-tensor])
144+
130145

131146
(defn buffered-image->dataset [^BufferedImage img]
132147
(let [width (.getWidth img)
133148
height (.getHeight img)
134-
raster (.getRaster img)
135-
num-bands (.getNumBands raster)
136-
137-
;; Pre-allocate arrays for each band's pixel data
138-
band-arrays (vec (repeatedly num-bands #(double-array (* width height))))
139-
140-
;; Read each band's data
141-
_ (doseq [band (range num-bands)]
142-
(.getSamples raster 0 0 width height band (nth band-arrays band)))
143-
144-
;; Create coordinate arrays
145-
xs (vec (for [_y (range height) x (range width)] x))
146-
ys (vec (for [y (range height) _x (range width)] y))
149+
num-bands (.getNumBands (.getRaster img))
150+
151+
;; The following will be easier after dtype-next's
152+
;; [#133](https://github.com/cnuernber/dtype-next/issues/133)
153+
;; is resolved.
154+
tensor (-> img
155+
dtype/->array-buffer
156+
;; height x width x bands
157+
(tensor/construct-tensor (dims/dimensions
158+
[width height num-bands]))
159+
;; bands x height x width
160+
(tensor/transpose [2 0 1]))
161+
162+
xs (dtype/as-reader
163+
(tensor/compute-tensor [height width]
164+
(fn [y x] x)))
165+
ys (dtype/as-reader
166+
(tensor/compute-tensor [height width]
167+
(fn [y x] y)))
147168

148169
;; Build dataset
149-
ds-map (into {:x xs :y ys}
150-
(map-indexed
151-
(fn [i arr] [(keyword (str "band-" i)) (vec arr)])
152-
band-arrays))]
153-
(tc/dataset ds-map)))
170+
ds-map (into {:x xs :y ys}
171+
(for [i (range num-bands)]
172+
[(keyword (str "band-" i))
173+
(dtype/as-reader
174+
(tensor i))]))]
175+
(-> tensor
176+
(tensor/reshape [(* height width)
177+
num-bands])
178+
ds-tensor/tensor->dataset
179+
(tc/add-columns {:x xs
180+
:y ys})
181+
(tc/select-columns (concat [:x :y]
182+
(range num-bands)))
183+
(tc/rename-columns (into {}
184+
(for [i (range num-bands)]
185+
[i (keyword (str "band-" i))]))))))
154186

155187
(defonce ds (-> example-geotiff-medium
156188
:images
@@ -182,7 +214,6 @@
182214
(defonce ds-with-approx-dvi (add-dvi ds))
183215

184216
(require '[tech.v3.dataset :as ds]
185-
'[tech.v3.datatype :as dtype]
186217
'[tech.v3.datatype.statistics :as dstats])
187218
(import '[java.awt Color])
188219

@@ -205,35 +236,22 @@
205236
(add-approx-dvi-display ds-with-approx-dvi))
206237

207238
(defn dataset->image
208-
[dataset {:keys [r g b]}]
209-
(let [xs (ds/column dataset :x)
210-
ys (ds/column dataset :y)
211-
rs (ds/column dataset r)
212-
gs (ds/column dataset g)
213-
bs (ds/column dataset b)
214-
215-
max-x (-> xs last int)
216-
min-x (-> xs first int)
217-
max-y (-> ys last int)
218-
min-y (-> ys first int)
219-
220-
width (inc (- max-x min-x))
221-
height (inc (- max-y min-y))
222-
223-
;; Create the image
224-
img (BufferedImage. width height BufferedImage/TYPE_INT_RGB)]
225-
226-
;; Set pixels
227-
(dotimes [i (ds/row-count dataset)]
228-
(let [x (- (int (dtype/get-value xs i)) min-x)
229-
y (- (int (dtype/get-value ys i)) min-y)
230-
r (int (dtype/get-value rs i))
231-
g (int (dtype/get-value gs i))
232-
b (int (dtype/get-value bs i))
233-
color (Color. r g b)]
234-
(.setRGB img x y (.getRGB color))))
235-
236-
img))
239+
[dataset {:as columns-mapping
240+
:keys [r g b]}]
241+
(let [height (->> dataset
242+
:y
243+
((juxt dstats/max dstats/min))
244+
(apply -))
245+
width (->> dataset
246+
:x
247+
((juxt dstats/max dstats/min))
248+
(apply -))]
249+
(-> columns-mapping
250+
(update-vals dataset)
251+
tc/dataset
252+
ds-tensor/dataset->tensor
253+
(tensor/reshape [height width 3])
254+
bufimg/tensor->image)))
237255

238256
(dataset->image ds-with-dvi-display
239257
{:r :dvi-display
@@ -408,7 +426,7 @@
408426
(defonce medium-geotiff
409427
(geotools-read-geotiff "src/gis/resources/example_geotiff_medium.tif"))
410428

411-
429+
412430

413431
(->> medium-geotiff
414432
:coverage

0 commit comments

Comments
 (0)