|
| 1 | +classdef OMEZarrAdapter < images.blocked.Adapter |
| 2 | + % OMEZarrAdapter A blockedImage adapter for Zarr files with OME data. |
| 3 | + % adapter = OMEZarrAdapter() creates a blockedImage adapter for use with |
| 4 | + % the blockedImage object. This adapter uses the "MATLAB support for |
| 5 | + % Zarr files" project to represent an OME formatted Zarr source as a |
| 6 | + % blockedImage object in MATLAB. |
| 7 | + % |
| 8 | + % Supported Name-Value pairs: |
| 9 | + % PermuteOrder 1xN array of integer indices specifying the data |
| 10 | + % dimension permutation order. Defaults to N:-1:1 |
| 11 | + % where N is the data dimensionality. Useful when raw |
| 12 | + % data does NOT adher to tczyx order. |
| 13 | + % |
| 14 | + % Example: |
| 15 | + % % Sample data downloaded from https://ome.github.io/ome-ngff-tools/ |
| 16 | + % zfile = "/data/work/zarr/9846318.zarr/0"; |
| 17 | + % bim = blockedImage(zfile, Adapter=OMEZarrAdapter) |
| 18 | + % bim.Size |
| 19 | + % imageshow(bim) |
| 20 | + |
| 21 | + % Copyright 2025 The MathWorks, Inc. |
| 22 | + |
| 23 | + properties (Access = private) |
| 24 | + ZarrRootPath (1,1) string |
| 25 | + Info (1,1) struct |
| 26 | + end |
| 27 | + |
| 28 | + properties(SetAccess=private) |
| 29 | + PermuteOrder double = []; |
| 30 | + end |
| 31 | + |
| 32 | + methods |
| 33 | + |
| 34 | + function obj = OMEZarrAdapter(options) |
| 35 | + arguments |
| 36 | + options.PermuteOrder double = []; |
| 37 | + end |
| 38 | + obj.PermuteOrder = options.PermuteOrder; |
| 39 | + end |
| 40 | + |
| 41 | + function openToRead(obj, zpath) |
| 42 | + obj.ZarrRootPath = zpath; |
| 43 | + end |
| 44 | + |
| 45 | + function info = getInfo(obj) |
| 46 | + zinfo = zarrinfo(obj.ZarrRootPath); |
| 47 | + |
| 48 | + assert(~isfield(zinfo,'plate'),"Plate format is not yet supported"); |
| 49 | + |
| 50 | + obj.Info.UserData.RootInfo = zinfo; |
| 51 | + |
| 52 | + if ~isfield(zinfo,"multiscales") |
| 53 | + error("OMEZarrAdapter:notMultiscale",... |
| 54 | + obj.ZarrRootPath+" does not have multiscale data."); |
| 55 | + end |
| 56 | + |
| 57 | + % TODO - Inspect and use "order" property (C (row major) or F (col major, rare)). |
| 58 | + |
| 59 | + for lvlInd = 1:numel(zinfo.multiscales.datasets) |
| 60 | + dzinfo = zarrinfo(obj.ZarrRootPath+"/"+zinfo.multiscales.datasets(lvlInd).path); |
| 61 | + obj.Info.UserData.DataSetInfo(lvlInd) = dzinfo; |
| 62 | + obj.Info.Size(lvlInd,:) = dzinfo.shape'; |
| 63 | + obj.Info.IOBlockSize(lvlInd,:) = dzinfo.chunks'; |
| 64 | + obj.Info.Datatype(lvlInd,:) = string(z2mtype(dzinfo.dtype)); |
| 65 | + end |
| 66 | + obj.Info.InitialValue = zeros(1,1,obj.Info.Datatype(1)); |
| 67 | + |
| 68 | + obj.Info.Size = obj.Info.Size(:,obj.PermuteOrder); |
| 69 | + obj.Info.IOBlockSize = obj.Info.IOBlockSize(:,obj.PermuteOrder); |
| 70 | + |
| 71 | + try |
| 72 | + for aInd = 1:numel(zinfo.multiscales.axes) |
| 73 | + obj.Info.UserData.Dimensions(aInd).Name = string(zinfo.multiscales.axes(aInd).name); |
| 74 | + obj.Info.UserData.Dimensions(aInd).Type = string(zinfo.multiscales.axes(aInd).type); |
| 75 | + if isfield(zinfo.multiscales.axes(aInd),'unit') |
| 76 | + obj.Info.UserData.Dimensions(aInd).Unit = string(zinfo.multiscales.axes(aInd).unit); |
| 77 | + else |
| 78 | + obj.Info.UserData.Dimensions(aInd).Unit = "none"; |
| 79 | + end |
| 80 | + end |
| 81 | + obj.Info.UserData.Dimensions = struct2table(obj.Info.UserData.Dimensions); |
| 82 | + catch ME |
| 83 | + % TODO - some files dont have this property, is that |
| 84 | + % allowed? |
| 85 | + % warning("OMEZarrAdapter:noAxes","No Axes information in multiscales property"); |
| 86 | + end |
| 87 | + |
| 88 | + |
| 89 | + % TODO - Make the default look at the existing order and figure |
| 90 | + % out the permutation to make it xyzct instead of just |
| 91 | + % flipping. (Note: Most files seem to be tczyx order, so below |
| 92 | + % ought to work just the same). Some files do not have this |
| 93 | + % axes information. |
| 94 | + if isempty(obj.PermuteOrder) |
| 95 | + obj.PermuteOrder = fliplr(1:numel(dzinfo.shape)); |
| 96 | + end |
| 97 | + if isfield(obj.Info.UserData,'Dimensions') |
| 98 | + obj.Info.UserData.Dimensions = obj.Info.UserData.Dimensions(obj.PermuteOrder,:); |
| 99 | + end |
| 100 | + |
| 101 | + % TODO - potentially update IOBlockSize for channels to expand |
| 102 | + % to 3 for RGB images (helps combine 3 getIOBlock calls to |
| 103 | + % one). |
| 104 | + |
| 105 | + info = obj.Info; |
| 106 | + end |
| 107 | + |
| 108 | + function data = getIOBlock(obj, ioblocksub, level) |
| 109 | + zpath = obj.ZarrRootPath+"/"+obj.Info.UserData.RootInfo.multiscales.datasets(level).path; |
| 110 | + start = (ioblocksub - 1) .* obj.Info.IOBlockSize(level,:) + 1; |
| 111 | + count = obj.Info.IOBlockSize(level,:); |
| 112 | + |
| 113 | + start = start(obj.PermuteOrder); |
| 114 | + count = count(obj.PermuteOrder); |
| 115 | + data = zarrread(zpath,'Start',start,'Count',count); |
| 116 | + |
| 117 | + % TODO - squeeze removes all singleton dimensions, could result |
| 118 | + % in a bug in the rare case of partial (1 element wide) blocks. |
| 119 | + data = squeeze(permute(data,obj.PermuteOrder)); |
| 120 | + end |
| 121 | + |
| 122 | + function data = getFullImage(obj, level) |
| 123 | + zpath = obj.ZarrRootPath+"/"+obj.Info.UserData.RootInfo.multiscales.datasets(level).path; |
| 124 | + data = zarrread(zpath); |
| 125 | + data = squeeze(permute(data,obj.PermuteOrder)); |
| 126 | + end |
| 127 | + |
| 128 | + end |
| 129 | +end |
| 130 | + |
| 131 | + |
| 132 | +% !LLM generated |
| 133 | +function mtype = z2mtype(ztype) |
| 134 | +% Z2MTYPE Converts a Zarr data type string to a MATLAB data type string. |
| 135 | +% |
| 136 | +% mtype = Z2MTYPE(ztype) |
| 137 | +% |
| 138 | +% Inputs: |
| 139 | +% ztype - A string representing the Zarr data type (e.g., '<u2', 'f4', 'b1'). |
| 140 | +% |
| 141 | +% Outputs: |
| 142 | +% mtype - A string representing the corresponding MATLAB data type |
| 143 | +% (e.g., 'uint16', 'single', 'logical'). |
| 144 | + |
| 145 | +% Remove endianness or byte order indicator for mapping |
| 146 | +if startsWith(ztype, '<') || startsWith(ztype, '>') || startsWith(ztype, '|') |
| 147 | + ztype_clean = ztype(2:end); |
| 148 | +else |
| 149 | + ztype_clean = ztype; |
| 150 | +end |
| 151 | + |
| 152 | +% Handle common Zarr data types |
| 153 | +switch lower(ztype_clean) |
| 154 | + case 'b1' |
| 155 | + mtype = 'logical'; |
| 156 | + case {'i1'} |
| 157 | + mtype = 'int8'; |
| 158 | + case {'u1'} |
| 159 | + mtype = 'uint8'; |
| 160 | + case {'i2'} |
| 161 | + mtype = 'int16'; |
| 162 | + case {'u2'} |
| 163 | + mtype = 'uint16'; |
| 164 | + case {'i4'} |
| 165 | + mtype = 'int32'; |
| 166 | + case {'u4'} |
| 167 | + mtype = 'uint32'; |
| 168 | + case {'i8'} |
| 169 | + mtype = 'int64'; |
| 170 | + case {'u8'} |
| 171 | + mtype = 'uint64'; |
| 172 | + case {'f4'} |
| 173 | + mtype = 'single'; |
| 174 | + case {'f8'} |
| 175 | + mtype = 'double'; |
| 176 | + otherwise |
| 177 | + % Unknown Zarr type |
| 178 | + error('OMEZarrAdapter:UnknownDataType', 'Unknown Zarr data type: %s. Returning empty string.', ztype); |
| 179 | +end |
| 180 | + |
| 181 | +end |
0 commit comments