@@ -5,11 +5,14 @@ import {
55 isBioformats2rawlayout ,
66 guessZarrVersion ,
77 isOmePlate ,
8+ isMultiscales ,
9+ coordinateTransformationsToMatrix ,
810} from '@hms-dbmi/vizarr/src/utils' ;
911import { FetchStore , open } from 'zarrita' ;
1012
1113import {
1214 findSeries ,
15+ getNgffAxes ,
1316 getXmlDom ,
1417 getZarrJson ,
1518 getZarrMetadata ,
@@ -34,99 +37,123 @@ const fetchSourceData = async (config) => {
3437 const zarrJson = zarrVersion === 3 ? await getZarrJson ( base ) : null ;
3538 let ome = zarrJson ?. attributes ?. ome || node . attrs ?. OME || null ;
3639
40+ let sourceData ;
3741 if (
3842 ! isBioformats2rawlayout ( ome || node . attrs ) ||
3943 isOmePlate ( ome || node . attrs ) // if plate is present it takes precedence (https://ngff.openmicroscopy.org/0.4/#bf2raw-attributes)
4044 ) {
4145 // use Vizarr's createSourceData with source as is
4246
43- if ( ome ?. version === " 0.5" ) {
44- const sourceData = await createSourceData ( config ) ;
47+ if ( ome ?. version === ' 0.5' ) {
48+ sourceData = await createSourceData ( config ) ;
4549 const labels = await resolveOmeLabelsFromMultiscales ( node ) ;
4650 sourceData . labels = await Promise . all (
4751 labels . map ( ( name ) => loadOmeImageLabel ( node . resolve ( 'labels' ) , name ) ) ,
4852 ) ;
49- return sourceData ;
53+ } else {
54+ sourceData = await createSourceData ( config ) ;
55+ }
56+ } else {
57+ // load bioformats2raw.layout
58+ // https://ngff.openmicroscopy.org/0.4/#bf2raw
59+
60+ // get b2f metadata from ome key in metadata or node attributes
61+ const b2fl =
62+ ome ?. [ 'bioformats2raw.layout' ] || node . attrs ?. [ 'bioformats2raw.layout' ] ;
63+ if ( b2fl !== 3 ) {
64+ throw new Error ( 'Unsupported bioformats2raw layout' ) ;
5065 }
51- return await createSourceData ( config ) ;
52- }
53-
54- // load bioformats2raw.layout
55- // https://ngff.openmicroscopy.org/0.4/#bf2raw
56-
57- // get b2f metadata from ome key in metadata or node attributes
58- const b2fl =
59- ome ?. [ 'bioformats2raw.layout' ] || node . attrs ?. [ 'bioformats2raw.layout' ] ;
60- if ( b2fl !== 3 ) {
61- throw new Error ( 'Unsupported bioformats2raw layout' ) ;
62- }
6366
64- // Try to load .zmetadata if present
65- const metadata = await getZarrMetadata ( base ) ;
67+ // Try to load .zmetadata if present
68+ const metadata = await getZarrMetadata ( base ) ;
6669
67- // Try to load OME group at root if present and not in v3 zarr metadata
68- if ( ! ome ) {
69- try {
70- ome = await open ( node . resolve ( 'OME' ) , { kind : 'group' } ) ;
71- } catch { }
72- }
70+ // Try to load OME group at root if present and not in v3 zarr metadata
71+ if ( ! ome ) {
72+ try {
73+ ome = await open ( node . resolve ( 'OME' ) , { kind : 'group' } ) ;
74+ } catch { }
75+ }
7376
74- // Try to load OME XML file if present
75- const omeXmlDom = await getXmlDom ( base ) ;
76- let omeXml = omeXmlDom ? parseXml ( omeXmlDom ) : null ;
77+ // Try to load OME XML file if present
78+ const omeXmlDom = await getXmlDom ( base ) ;
79+ let omeXml = omeXmlDom ? parseXml ( omeXmlDom ) : null ;
7780
78- let series ;
79- if ( ome ?. series ) {
80- series = ome . series ;
81- } else if ( ome ?. attrs ?. series ) {
82- series = ome . attrs . series ;
83- } else {
84- // https://ngff.openmicroscopy.org/0.4/#bf2raw-details
85- if ( metadata ) {
86- const multiscaleKeys = Object . keys ( metadata ) . filter (
87- ( key ) => key . endsWith ( '/.zattrs' ) && 'multiscales' in metadata [ key ] ,
88- ) ;
89- series = multiscaleKeys . map ( ( key ) => key . split ( '/' ) [ 0 ] ) ;
90- } else if ( omeXml ) {
91- series = omeXml . images . map ( ( image ) => image . path ) ;
81+ let series ;
82+ if ( ome ?. series ) {
83+ series = ome . series ;
84+ } else if ( ome ?. attrs ?. series ) {
85+ series = ome . attrs . series ;
9286 } else {
93- console . warn (
94- 'No OME group, .zmetadata or xml file. Attempting to find series.' ,
95- ) ;
96- series = await findSeries ( base , node , zarrVersion ) ;
87+ // https://ngff.openmicroscopy.org/0.4/#bf2raw-details
88+ if ( metadata ) {
89+ const multiscaleKeys = Object . keys ( metadata ) . filter (
90+ ( key ) => key . endsWith ( '/.zattrs' ) && 'multiscales' in metadata [ key ] ,
91+ ) ;
92+ series = multiscaleKeys . map ( ( key ) => key . split ( '/' ) [ 0 ] ) ;
93+ } else if ( omeXml ) {
94+ series = omeXml . images . map ( ( image ) => image . path ) ;
95+ } else {
96+ console . warn (
97+ 'No OME group, .zmetadata or xml file. Attempting to find series.' ,
98+ ) ;
99+ series = await findSeries ( base , node , zarrVersion ) ;
100+ }
97101 }
102+
103+ const seriesMd = await Promise . all (
104+ series ?. map ( async ( s , index ) => {
105+ const seriesNode = await open ( node . resolve ( s ) , {
106+ kind : 'group' ,
107+ } ) ;
108+ if ( ! seriesNode . attrs . multiscales ?. [ 0 ] . axes && omeXml ) {
109+ // get axes from xml if not in metadata
110+ // "The specified dimension order is then reversed when creating Zarr arrays, e.g. XYCZT would become TZCYX in Zarr." (https://github.com/glencoesoftware/bioformats2raw/blob/85ef84db26ce1239dd71ef482b4f38f67e605491/README.md?plain=1#L293)
111+ // though multiscales metadata MUST have axes (https://ngff.openmicroscopy.org/0.4/#multiscale-md)
112+ const dimensionOrder = omeXml . images [ index ] . dimensionOrder ;
113+ return dimensionOrder
114+ ? {
115+ channel_axis :
116+ dimensionOrder ?. length - dimensionOrder ?. indexOf ( 'C' ) - 1 ,
117+ }
118+ : { } ;
119+ }
120+ return { } ;
121+ } ) ,
122+ ) ;
123+
124+ // @TODO : return all series
125+ const sIndex = 0 ;
126+
127+ const seriesUrl = `${ base . replace ( / \/ ? $ / , '/' ) } ${ series ?. [ sIndex ] || '' } ` ;
128+ sourceData = await createSourceData ( {
129+ ...config ,
130+ source : seriesUrl ,
131+ ...seriesMd [ sIndex ] ,
132+ } ) ;
98133 }
99134
100- const seriesMd = await Promise . all (
101- series ?. map ( async ( s , index ) => {
102- const seriesNode = await open ( node . resolve ( s ) , {
103- kind : 'group' ,
104- } ) ;
105- if ( ! seriesNode . attrs . multiscales ?. [ 0 ] . axes && omeXml ) {
106- // get axes from xml if not in metadata
107- // "The specified dimension order is then reversed when creating Zarr arrays, e.g. XYCZT would become TZCYX in Zarr." (https://github.com/glencoesoftware/bioformats2raw/blob/85ef84db26ce1239dd71ef482b4f38f67e605491/README.md?plain=1#L293)
108- // though multiscales metadata MUST have axes (https://ngff.openmicroscopy.org/0.4/#multiscale-md)
109- const dimensionOrder = omeXml . images [ index ] . dimensionOrder ;
110- return dimensionOrder
111- ? {
112- channel_axis :
113- dimensionOrder ?. length - dimensionOrder ?. indexOf ( 'C' ) - 1 ,
114- }
115- : { } ;
116- }
117- return { } ;
118- } ) ,
119- ) ;
120-
121- // @TODO : return all series
122- const sIndex = 0 ;
123-
124- const seriesUrl = `${ base . replace ( / \/ ? $ / , '/' ) } ${ series ?. [ sIndex ] || '' } ` ;
125- return await createSourceData ( {
126- ...config ,
127- source : seriesUrl ,
128- ...seriesMd [ sIndex ] ,
129- } ) ;
135+ // @TODO : implement this in createSourceData
136+ // Get physical sizes and add them to loader.meta (or as another prop?)
137+ const attrs = ome || node . attrs ;
138+ if ( isMultiscales ( attrs ) ) {
139+ const axes = getNgffAxes ( attrs . multiscales ) ;
140+ const ct = coordinateTransformationsToMatrix ( attrs . multiscales ) ;
141+ const matrixIndices = {
142+ x : 0 ,
143+ y : 5 ,
144+ z : 10 ,
145+ } ;
146+ const physicalSizes = axes
147+ . filter ( ( a ) => a . type === 'space' )
148+ . reduce ( ( acc , a ) => {
149+ acc [ a . name ] = { size : ct [ matrixIndices [ a . name ] ] , unit : a . unit } ;
150+ return acc ;
151+ } , { } ) ;
152+ // @TODO : get t size from multiscales.coordinateTransformations if axis is present
153+ sourceData . loader [ 0 ] . meta = { physicalSizes } ;
154+ }
155+
156+ return sourceData ;
130157 } catch ( err ) {
131158 throw err ;
132159 }
0 commit comments