1
- import { getImageSize } from "@hms-dbmi/viv" ;
2
- import { BitmapLayer , TileLayer , type UpdateParameters } from "deck.gl" ;
1
+ import { BitmapLayer , TileLayer } from "deck.gl" ;
3
2
import * as utils from "../utils" ;
4
3
4
+ import type { Layer , UpdateParameters } from "deck.gl" ;
5
5
import { type Matrix4 , clamp } from "math.gl" ;
6
6
import type * as zarr from "zarrita" ;
7
7
import type { ZarrPixelSource } from "../ZarrPixelSource" ;
8
8
9
9
type Texture = ReturnType < BitmapLayer [ "context" ] [ "device" ] [ "createTexture" ] > ;
10
10
11
- export type LabelLayerLut = Readonly < Record < number , readonly [ number , number , number , number ] > > ;
11
+ export type OmeColors = Readonly < Record < number , readonly [ number , number , number , number ] > > ;
12
12
export interface LabelLayerProps {
13
13
id : string ;
14
14
loader : Array < ZarrPixelSource > ;
15
15
selection : Array < number > ;
16
16
opacity : number ;
17
17
modelMatrix : Matrix4 ;
18
- lut ?: LabelLayerLut ;
18
+ colors ?: OmeColors ;
19
19
}
20
20
21
21
/**
@@ -35,12 +35,18 @@ type LabelPixelData = {
35
35
height : number ;
36
36
} ;
37
37
38
- export class LabelLayer extends TileLayer < LabelPixelData > {
38
+ export class LabelLayer extends TileLayer < LabelPixelData , LabelLayerProps > {
39
39
static layerName = "VizarrLabelLayer" ;
40
+ // @ts -expect-error - only way to extend the base state type
41
+ state ! : { colorTexture : Texture } & TileLayer [ "state" ] ;
40
42
41
43
constructor ( props : LabelLayerProps ) {
42
44
const resolutions = props . loader ;
43
- const dimensions = getImageSize ( resolutions [ 0 ] ) ;
45
+ const dimensions = {
46
+ height : resolutions [ 0 ] . shape . at ( - 2 ) ,
47
+ width : resolutions [ 0 ] . shape . at ( - 1 ) ,
48
+ } ;
49
+ utils . assert ( dimensions . width && dimensions . height ) ;
44
50
const tileSize = getTileSizeForResolutions ( resolutions ) ;
45
51
super ( {
46
52
id : `labels-${ props . id } ` ,
@@ -50,6 +56,7 @@ export class LabelLayer extends TileLayer<LabelPixelData> {
50
56
opacity : props . opacity ,
51
57
maxZoom : 0 ,
52
58
modelMatrix : props . modelMatrix ,
59
+ colors : props . colors ,
53
60
zoomOffset : Math . round ( Math . log2 ( props . modelMatrix ? props . modelMatrix . getScale ( ) [ 0 ] : 1 ) ) ,
54
61
updateTriggers : {
55
62
getTileData : [ props . loader , props . selection ] ,
@@ -65,29 +72,68 @@ export class LabelLayer extends TileLayer<LabelPixelData> {
65
72
) ;
66
73
return { data, width, height } ;
67
74
} ,
68
- renderSubLayers ( { tile, data } ) {
69
- const [ [ left , bottom ] , [ right , top ] ] = tile . boundingBox ;
70
- const { width, height } = dimensions ;
71
- return new GrayscaleBitmapLayer ( {
72
- id : `tile-${ tile . index . x } .${ tile . index . y } .${ tile . index . z } -${ props . id } ` ,
73
- pixelData : data ,
74
- opacity : props . opacity ,
75
- modelMatrix : props . modelMatrix ,
76
- lut : props . lut ,
77
- bounds : [ clamp ( left , 0 , width ) , clamp ( top , 0 , height ) , clamp ( right , 0 , width ) , clamp ( bottom , 0 , height ) ] ,
78
- // For underlying class
79
- image : new ImageData ( data . width , data . height ) ,
80
- pickable : false ,
81
- } ) ;
82
- } ,
83
75
} ) ;
84
76
}
77
+
78
+ renderSubLayers (
79
+ params : TileLayer [ "props" ] & {
80
+ data : LabelPixelData ;
81
+ tile : {
82
+ index : { x : number ; y : number ; z : number } ;
83
+ boundingBox : [ min : Array < number > , max : Array < number > ] ;
84
+ } ;
85
+ } ,
86
+ ) : Layer {
87
+ const { tile, data, ...props } = params ;
88
+ const [ [ left , bottom ] , [ right , top ] ] = tile . boundingBox ;
89
+ utils . assert ( props . extent , "missing extent" ) ;
90
+ const [ _x0 , _y0 , width , height ] = props . extent ;
91
+ return new GrayscaleBitmapLayer ( {
92
+ id : `tile-${ tile . index . x } .${ tile . index . y } .${ tile . index . z } -${ props . id } ` ,
93
+ pixelData : data ,
94
+ opacity : props . opacity ,
95
+ modelMatrix : props . modelMatrix ,
96
+ colorTexture : this . state . colorTexture ,
97
+ bounds : [ clamp ( left , 0 , width ) , clamp ( top , 0 , height ) , clamp ( right , 0 , width ) , clamp ( bottom , 0 , height ) ] ,
98
+ // For underlying class
99
+ image : new ImageData ( data . width , data . height ) ,
100
+ pickable : false ,
101
+ } ) ;
102
+ }
103
+
104
+ updateState ( { props, oldProps, changeFlags, ...rest } : UpdateParameters < this> ) : void {
105
+ super . updateState ( { props, oldProps, changeFlags, ...rest } ) ;
106
+ // we make the colorTexture on this layer so we can share it amoung all the sublayers
107
+ if ( props . colors !== oldProps . colors || ! this . state . colorTexture ) {
108
+ this . state . colorTexture ?. destroy ( ) ;
109
+ const colorTexture = createColorTexture ( {
110
+ source : props . colors ,
111
+ maxTextureDimension2D : this . context . device . limits . maxTextureDimension2D ,
112
+ } ) ;
113
+ this . setState ( {
114
+ colorTexture : this . context . device . createTexture ( {
115
+ width : colorTexture . width ,
116
+ height : colorTexture . height ,
117
+ data : colorTexture . data ,
118
+ dimension : "2d" ,
119
+ mipmaps : false ,
120
+ sampler : {
121
+ minFilter : "nearest" ,
122
+ magFilter : "nearest" ,
123
+ addressModeU : "clamp-to-edge" ,
124
+ addressModeV : "clamp-to-edge" ,
125
+ } ,
126
+ format : "rgba8unorm" ,
127
+ } ) ,
128
+ } ) ;
129
+ }
130
+ }
85
131
}
86
132
87
- export class GrayscaleBitmapLayer extends BitmapLayer < { pixelData : LabelPixelData ; lut ?: LabelLayerLut } > {
133
+ export class GrayscaleBitmapLayer extends BitmapLayer < { pixelData : LabelPixelData ; colorTexture : Texture } > {
88
134
static layerName = "VizarrGrayscaleBitmapLayer" ;
89
135
// @ts -expect-error - only way to extend the base state type
90
- state ! : { texture : Texture ; colorTexture : Texture } & BitmapLayer [ "state" ] ;
136
+ state ! : { texture : Texture } & BitmapLayer [ "state" ] ;
91
137
92
138
getShaders ( ) {
93
139
const sampler = (
@@ -162,40 +208,21 @@ void main() {
162
208
} ) ,
163
209
} ) ;
164
210
}
165
-
166
- if ( props . lut !== oldProps . lut || ! this . state . colorTexture ) {
167
- this . state . colorTexture ?. destroy ( ) ;
168
- const colorTexture = createColorTexture ( {
169
- source : props . lut ,
170
- maxTextureDimension2D : this . context . device . limits . maxTextureDimension2D ,
171
- } ) ;
172
- this . setState ( {
173
- colorTexture : this . context . device . createTexture ( {
174
- width : colorTexture . width ,
175
- height : colorTexture . height ,
176
- data : colorTexture . data ,
177
- dimension : "2d" ,
178
- mipmaps : false ,
179
- sampler : {
180
- minFilter : "nearest" ,
181
- magFilter : "nearest" ,
182
- addressModeU : "clamp-to-edge" ,
183
- addressModeV : "clamp-to-edge" ,
184
- } ,
185
- format : "rgba8unorm" ,
186
- } ) ,
187
- } ) ;
188
- }
189
211
}
190
212
191
213
draw ( opts : unknown ) {
192
- const { model, texture, colorTexture } = this . state ;
214
+ const { model, texture } = this . state ;
215
+ const { colorTexture } = this . props ;
216
+
193
217
if ( model && texture && colorTexture ) {
194
218
model . setUniforms ( {
195
219
colorTextureWidth : colorTexture . width ,
196
220
colorTextureHeight : colorTexture . height ,
197
221
} ) ;
198
- model . setBindings ( { grayscaleTexture : texture , colorTexture : colorTexture } ) ;
222
+ model . setBindings ( {
223
+ grayscaleTexture : texture ,
224
+ colorTexture : colorTexture ,
225
+ } ) ;
199
226
}
200
227
super . draw ( opts ) ;
201
228
}
@@ -210,7 +237,7 @@ function getTileSizeForResolutions(resolutions: Array<ZarrPixelSource>): number
210
237
return tileSize ;
211
238
}
212
239
213
- const SEEN_LUTS = new WeakSet < LabelLayerLut > ( ) ;
240
+ const SEEN_LUTS = new WeakSet < OmeColors > ( ) ;
214
241
215
242
/**
216
243
* Creates a color lookup table (LUT) as a 2D texture.
@@ -219,7 +246,7 @@ const SEEN_LUTS = new WeakSet<LabelLayerLut>();
219
246
* @param options.maxTextureDimension2D - The maximum texture dimension size.
220
247
*/
221
248
function createColorTexture ( options : {
222
- source ?: LabelLayerLut ;
249
+ source ?: OmeColors ;
223
250
maxTextureDimension2D : number ;
224
251
} ) {
225
252
const { source, maxTextureDimension2D } = options ;
0 commit comments