|
10 | 10 |
|
11 | 11 | (ns gis.geotiff |
12 | 12 | (: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])) |
14 | 21 | ;; # Cloud Optimized GeoTIFFs in JVM Clojure |
15 | 22 | ;; ## Motivation |
16 | 23 | ;; This document shows several different methods for handling COGs in JVM Clojure without |
|
33 | 40 | [:img {:src "resources/tiff_geotiff_cog.png" |
34 | 41 | :style {:width "60%"}}]) |
35 | 42 |
|
| 43 | + |
| 44 | + |
36 | 45 | ;; 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/). |
37 | 46 |
|
38 | 47 | ;; # TIFF handling with built-in Java libraries |
|
72 | 81 | {:images images |
73 | 82 | :reader-format (.getFormatName reader)})))) |
74 | 83 |
|
| 84 | +(require '[tech.v3.datatype :as dtype]) |
| 85 | + |
75 | 86 | (defn get-pixel [^BufferedImage image x y] |
76 | 87 | (let [raster (.getRaster image) |
77 | 88 | ;; Handle images with an arbitrary number of bands. |
78 | 89 | ;; Remote sensing imagery is often `multispectral`. |
79 | 90 | num-bands (.getNumBands raster) |
80 | 91 | pixel (double-array num-bands)] |
81 | 92 | (.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))) |
83 | 95 |
|
84 | 96 | ;; Now we use the above helper functions to handle our locally held TIFF. |
85 | 97 |
|
|
126 | 138 | ;; For our purposes we can just add the tablecloth dependency and that will transitively |
127 | 139 | ;; bring along the rest of the stack. |
128 | 140 |
|
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 | + |
130 | 145 |
|
131 | 146 | (defn buffered-image->dataset [^BufferedImage img] |
132 | 147 | (let [width (.getWidth img) |
133 | 148 | 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))) |
147 | 168 |
|
148 | 169 | ;; 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))])))))) |
154 | 186 |
|
155 | 187 | (defonce ds (-> example-geotiff-medium |
156 | 188 | :images |
|
182 | 214 | (defonce ds-with-approx-dvi (add-dvi ds)) |
183 | 215 |
|
184 | 216 | (require '[tech.v3.dataset :as ds] |
185 | | - '[tech.v3.datatype :as dtype] |
186 | 217 | '[tech.v3.datatype.statistics :as dstats]) |
187 | 218 | (import '[java.awt Color]) |
188 | 219 |
|
|
205 | 236 | (add-approx-dvi-display ds-with-approx-dvi)) |
206 | 237 |
|
207 | 238 | (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))) |
237 | 255 |
|
238 | 256 | (dataset->image ds-with-dvi-display |
239 | 257 | {:r :dvi-display |
|
408 | 426 | (defonce medium-geotiff |
409 | 427 | (geotools-read-geotiff "src/gis/resources/example_geotiff_medium.tif")) |
410 | 428 |
|
411 | | - |
| 429 | + |
412 | 430 |
|
413 | 431 | (->> medium-geotiff |
414 | 432 | :coverage |
|
0 commit comments