1616
1717import type { CoordinateSpace } from "#src/coordinate_transform.js" ;
1818import { makeCoordinateSpace } from "#src/coordinate_transform.js" ;
19+ import type {
20+ SingleChannelMetadata ,
21+ ChannelMetadata ,
22+ } from "#src/datasource/index.js" ;
23+ import { parseRGBColorSpecification } from "#src/util/color.js" ;
1924import {
2025 parseArray ,
2126 parseFixedLengthArray ,
27+ verifyBoolean ,
2228 verifyFiniteFloat ,
2329 verifyFinitePositiveFloat ,
2430 verifyObject ,
2531 verifyObjectProperty ,
2632 verifyOptionalObjectProperty ,
2733 verifyString ,
2834} from "#src/util/json.js" ;
35+ import { clampToInterval } from "#src/util/lerp.js" ;
2936import * as matrix from "#src/util/matrix.js" ;
3037import { allSiPrefixes } from "#src/util/si_units.js" ;
3138
@@ -39,6 +46,11 @@ export interface OmeMultiscaleMetadata {
3946 coordinateSpace : CoordinateSpace ;
4047}
4148
49+ export interface OmeMetadata {
50+ multiscale : OmeMultiscaleMetadata ;
51+ channels : ChannelMetadata | undefined ;
52+ }
53+
4254const SUPPORTED_OME_MULTISCALE_VERSIONS = new Set ( [ "0.4" , "0.5-dev" , "0.5" ] ) ;
4355
4456const OME_UNITS = new Map < string , { unit : string ; scale : number } > ( [
@@ -72,6 +84,75 @@ interface Axis {
7284 type : string | undefined ;
7385}
7486
87+ function parseOmeroChannel ( omeroChannel : unknown ) : SingleChannelMetadata {
88+ verifyObject ( omeroChannel ) ;
89+
90+ const getProp = < T > (
91+ key : string ,
92+ verifier : ( value : unknown ) => T ,
93+ ) : T | undefined => verifyOptionalObjectProperty ( omeroChannel , key , verifier ) ;
94+ const inputWindow = getProp ( "window" , verifyObject ) ;
95+ const getWindowProp = < T > (
96+ key : string ,
97+ verifier : ( value : unknown ) => T ,
98+ ) : T | undefined =>
99+ inputWindow
100+ ? verifyOptionalObjectProperty ( inputWindow , key , verifier )
101+ : undefined ;
102+
103+ const active = getProp ( "active" , verifyBoolean ) ;
104+ const coefficient = getProp ( "coefficient" , verifyFiniteFloat ) ;
105+ let colorString = getProp ( "color" , verifyString ) ;
106+ // If six hex digits, needs the # in front of the hex color
107+ if ( colorString && / ^ [ 0 - 9 a - f ] { 6 } $ / i. test ( colorString ) ) {
108+ colorString = `#${ colorString } ` ;
109+ }
110+ const color = parseRGBColorSpecification ( colorString ) ;
111+ const inverted = getProp ( "inverted" , verifyBoolean ) ;
112+ const label = getProp ( "label" , verifyString ) ;
113+
114+ const windowMin = getWindowProp ( "min" , verifyFiniteFloat ) ;
115+ const windowMax = getWindowProp ( "max" , verifyFiniteFloat ) ;
116+ const windowStart = getWindowProp ( "start" , verifyFiniteFloat ) ;
117+ const windowEnd = getWindowProp ( "end" , verifyFiniteFloat ) ;
118+
119+ const window =
120+ windowMin !== undefined && windowMax !== undefined
121+ ? ( [ windowMin , windowMax ] as [ number , number ] )
122+ : undefined ;
123+
124+ const range =
125+ windowStart !== undefined && windowEnd !== undefined
126+ ? inverted
127+ ? ( [ windowEnd , windowStart ] as [ number , number ] )
128+ : ( [ windowStart , windowEnd ] as [ number , number ] )
129+ : undefined ;
130+ // If there is a window, then clamp the range to the window.
131+ if ( window !== undefined && range !== undefined ) {
132+ range [ 0 ] = clampToInterval ( window , range [ 0 ] ) as number ;
133+ range [ 1 ] = clampToInterval ( window , range [ 1 ] ) as number ;
134+ }
135+
136+ return {
137+ active,
138+ label,
139+ color,
140+ coefficient,
141+ range,
142+ window,
143+ } ;
144+ }
145+
146+ function parseOmeroMetadata ( omero : unknown ) : ChannelMetadata {
147+ verifyObject ( omero ) ;
148+ const name = verifyOptionalObjectProperty ( omero , "name" , verifyString ) ;
149+ const channels = verifyObjectProperty ( omero , "channels" , ( x ) =>
150+ parseArray ( x , parseOmeroChannel ) ,
151+ ) ;
152+
153+ return { name, channels } ;
154+ }
155+
75156function parseOmeAxis ( axis : unknown ) : Axis {
76157 verifyObject ( axis ) ;
77158 const name = verifyObjectProperty ( axis , "name" , verifyString ) ;
@@ -266,9 +347,10 @@ export function parseOmeMetadata(
266347 url : string ,
267348 attrs : any ,
268349 zarrVersion : number ,
269- ) : OmeMultiscaleMetadata | undefined {
350+ ) : OmeMetadata | undefined {
270351 const ome = attrs . ome ;
271352 const multiscales = ome == undefined ? attrs . multiscales : ome . multiscales ; // >0.4
353+ const omero = attrs . omero ;
272354
273355 if ( ! Array . isArray ( multiscales ) ) return undefined ;
274356 const errors : string [ ] = [ ] ;
@@ -301,7 +383,9 @@ export function parseOmeMetadata(
301383 ) ;
302384 continue ;
303385 }
304- return parseOmeMultiscale ( url , multiscale ) ;
386+ const multiScaleInfo = parseOmeMultiscale ( url , multiscale ) ;
387+ const channelMetadata = omero ? parseOmeroMetadata ( omero ) : undefined ;
388+ return { multiscale : multiScaleInfo , channels : channelMetadata } ;
305389 }
306390 if ( errors . length !== 0 ) {
307391 throw new Error ( errors [ 0 ] ) ;
0 commit comments