Skip to content

Commit 106260d

Browse files
committed
Merge branch 'master' into pr/1
# Conflicts: # @FocusStack/OpenFiles.m # @TIFFStack/TIFFStack.m # @TIFFStack/private/tiffread31_header.m
2 parents 58aa355 + 3ee2118 commit 106260d

File tree

2 files changed

+226
-13
lines changed

2 files changed

+226
-13
lines changed

@TIFFStack/TIFFStack.m

Lines changed: 225 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,18 @@
123123
%
124124
% -------------------------------------------------------------------------
125125
%
126+
% ImageJ stacks
127+
%
128+
% ImageJ HyperStacks are automatically deinterleaved, if encountered. By
129+
% default, the stacks will be presented as [Y X T Z C]. They can of course
130+
% be permuted. If desired, the interleaving can be overridden by providing
131+
% an explicit 'vnInterleavedFrameDims'.
132+
%
133+
% ImageJ writes "fake" big stacks as raw binary data, with a TIF file shim
134+
% as a header. These appear to Matlab as a TIF file containing a single
135+
% frame. TIFFStack loads these files using MappedTensor, when available. If
136+
% MappedTensor is not available, a warning will be issued.
137+
%
126138
% References:
127139
% [1] Francois Nedelec, Thomas Surrey and A.C. Maggs. Physical Review Letters
128140
% 86: 3192-3195; 2001. DOI: 10.1103/PhysRevLett.86.3192
@@ -147,9 +159,10 @@
147159
bForceTiffread % - Force the use of tiffread, rather than trying to use TiffLib
148160
vnDataSize; % - Cached size of the TIFF stack
149161
vnApparentSize; % - Apparent size of the TIFF stack
150-
TIF; % \_ Cached header info for tiffread29 speedups
162+
TIF; % \_ Cached header info for tiffread31 speedups
151163
HEADER; % /
152164
bUseTiffLib; % - Flag indicating whether TiffLib is being used
165+
bMTStack; % - Flag indicating MappedTensor is being used
153166
fhReadFun; % - When using Tiff class, function for reading data
154167
fhSetDirFun; % - When using Tiff class, function for setting the directory
155168
vnDimensionOrder; % - Internal dimensions order to support permutation
@@ -221,7 +234,35 @@
221234
sInfo = imfinfo(strFilename);
222235
oStack.sImageInfo = sInfo;
223236

224-
if (oStack.bUseTiffLib)
237+
% - Detect a ImageJ fake BigTIFF stack
238+
[bIsImageJBigStack, bIsImageJHyperStack, vnStackDims, vnInterleavedIJFrameDims] = IsImageJBigStack(sInfo);
239+
240+
% - Handle ImageJ big stacks with MappedTensor
241+
if (bIsImageJBigStack)
242+
[oStack.TIF, oStack.bMTStack, oStack.strDataClass] = OpenImageJBigStack(oStack, vnStackDims);
243+
244+
% - Could we use a MappedTensor?
245+
if (~oStack.bMTStack)
246+
% - No, so just access the first frame
247+
bIsImageJBigStack = false; %#ok<NASGU>
248+
bIsImageJHyperStack = false;
249+
250+
warning('TIFFStack:ImageJBigStackUnsupported', ...
251+
'--- TIFFStack: This is an ImageJ "fake" TIF file. MappedTensor must be available to read this file.');
252+
end
253+
end
254+
255+
% - Deinterleave hyperstacks automatically
256+
if (bIsImageJHyperStack && isempty(vnInterleavedFrameDims))
257+
vnInterleavedFrameDims = vnInterleavedIJFrameDims;
258+
end
259+
260+
% - Initialise object, depending on underlying access method
261+
if (oStack.bMTStack)
262+
% - Fix up stack size
263+
sInfo = repmat(sInfo, vnStackDims(3), 1);
264+
265+
elseif (oStack.bUseTiffLib)
225266
% - Create a Tiff object
226267
oStack.TIF = tifflib('open', strFilename, 'r');
227268

@@ -341,7 +382,7 @@
341382
end
342383

343384
else
344-
% - Read TIFF header for tiffread29
385+
% - Read TIFF header for tiffread31
345386
[oStack.TIF, oStack.HEADER] = tiffread31_header(strFilename);
346387

347388
% - Use tiffread29 to get the data class for this tiff
@@ -382,6 +423,11 @@
382423

383424
% - Initialise dimension order
384425
oStack.vnDimensionOrder = 1:numel(oStack.vnApparentSize);
426+
427+
% - Fix up dimensions order for ImageJ HyperStack
428+
if (bIsImageJHyperStack)
429+
oStack = permute(oStack, [1 2 5 4 3]);
430+
end
385431

386432
catch mErr
387433
base_ME = MException('TIFFStack:InvalidFile', ...
@@ -411,10 +457,14 @@ function delete(oStack)
411457
function diagnostic(oStack)
412458
disp(oStack);
413459
fprintf('<strong>Private properties:</strong>\n');
460+
fprintf(' vnDataSize: ['); fprintf('%d ', oStack.vnDataSize); fprintf(']\n');
461+
fprintf(' vnApparentSize: ['); fprintf('%d ', oStack.vnApparentSize); fprintf(']\n');
462+
fprintf(' vnDimensionOrder: ['); fprintf('%d ', oStack.vnDimensionOrder); fprintf(']\n');
414463
fprintf(' bUseTiffLib: %d\n', oStack.bUseTiffLib);
415464
fprintf(' fhReadFun: %s\n', func2str(oStack.fhReadFun));
416-
fprintf(' vnDimensionOrder: ['); fprintf('%d ', oStack.vnDimensionOrder); fprintf(']\n');
465+
fprintf(' fhSetDirFun: %s\n', func2str(oStack.fhSetDirFun));
417466
fprintf(' fhRepSum: %s\n', func2str(oStack.fhRepSum));
467+
fprintf(' fhCastFun: %s\n', func2str(oStack.fhCastFun));
418468
end
419469

420470
%% --- Overloaded subsref
@@ -500,9 +550,9 @@ function diagnostic(oStack)
500550
vnRetDataSize(vbIsColon) = vnReferencedTensorSize(vbIsColon);
501551

502552
% - Permute index order
503-
vnInvOrder(oStack.vnDimensionOrder(1:nNumNZStackDims)) = 1:nNumNZStackDims;
504-
S.subs = S.subs(vnInvOrder(vnInvOrder ~= 0));
505553
S.subs(nNumNZStackDims+1:nNumTotalStackDims) = {1};
554+
vnInvOrder(oStack.vnDimensionOrder(1:nNumTotalStackDims)) = 1:nNumTotalStackDims;
555+
S.subs = S.subs(vnInvOrder(vnInvOrder ~= 0));
506556

507557
else % (nNumRefDims > nNumNZStackDims)
508558
% - Check for non-colon references
@@ -572,12 +622,14 @@ function diagnostic(oStack)
572622
end
573623

574624
% - Construct referencing subscripts for raw stack
575-
S.subs = [S.subs(1:2) {vnFrameIndices(:)} S.subs(end)];
625+
S.subs = [S.subs(1:2) reshape(vnFrameIndices, 1, []) S.subs(end)];
576626
end
577627
end
578628

579-
% - Access stack (tifflib or tiffread)
580-
if (oStack.bUseTiffLib)
629+
% - Access stack (MappedTensor or tifflib or tiffread)
630+
if (oStack.bMTStack)
631+
tfData = TS_read_data_MappedTensor(oStack, S.subs, bLinearIndexing);
632+
elseif (oStack.bUseTiffLib)
581633
tfData = TS_read_data_Tiff(oStack, S.subs, bLinearIndexing);
582634
else
583635
tfData = TS_read_data_tiffread(oStack, S.subs, bLinearIndexing);
@@ -641,7 +693,7 @@ function diagnostic(oStack)
641693
% - Trim trailing unitary dimensions
642694
vbIsUnitary = vnSize == 1;
643695
if (vbIsUnitary(end))
644-
nLastNonUnitary = find(vnSize == 1, 1, 'last') - 1;
696+
nLastNonUnitary = numel(vnSize) - find(fliplr(vnSize) == 1, 1, 'last');
645697
if (nLastNonUnitary < numel(vnSize))
646698
vnSize = vnSize(1:nLastNonUnitary);
647699
end
@@ -761,6 +813,23 @@ function diagnostic(oStack)
761813

762814
end
763815

816+
%% -- Overloaded load method
817+
818+
methods (Static)
819+
820+
% loadobj - Load method
821+
function oStack = loadobj(sSavedVar)
822+
% - Create a new TIFFStack
823+
oStack = TIFFStack(sSavedVar.strFilename, sSavedVar.bInvert, [], ...
824+
sSavedVar.bForceTiffread);
825+
826+
% - Adjust dimensions to look like saved stack
827+
oStack.vnApparentSize = sSavedVar.vnApparentSize;
828+
oStack.vnDimensionOrder = sSavedVar.vnDimensionOrder;
829+
end
830+
831+
end
832+
764833
%% -- Overloaded load method
765834

766835
methods (Static)
@@ -984,6 +1053,74 @@ function diagnostic(oStack)
9841053
end
9851054
end
9861055

1056+
1057+
% TS_read_data_MappedTensor - FUNCTION Read the requested pixels from an ImageJ fake binary TIFF file (using MappedTensor)
1058+
%
1059+
% Usage: [tfData] = TS_read_data_MappedTensor(oStack, cIndices)
1060+
%
1061+
% 'oStack' is a TIFFStack. 'cIndices' are the indices passed in from subsref.
1062+
% Colon indexing will be converted to full range indexing. cIndices is a cell
1063+
% array with the format {rows, cols, frames, slices}. Slices are RGB or CMYK
1064+
% or so on.
1065+
1066+
function [tfData] = TS_read_data_MappedTensor(oStack, cIndices, bLinearIndexing)
1067+
1068+
% - Fix up subsample detection for unitary dimensions
1069+
vbIsColon = cellfun(@iscolon, cIndices);
1070+
vbIsOne = cellfun(@(c)isequal(c, 1), cIndices(~vbIsColon));
1071+
vbIsColon(~vbIsColon) = vbIsOne & (oStack.vnDataSize(~vbIsColon) == 1);
1072+
1073+
% - Check ranges
1074+
vnMinRange = cellfun(@(c)(min(c(:))), cIndices(~vbIsColon));
1075+
vnMaxRange = cellfun(@(c)(max(c(:))), cIndices(~vbIsColon));
1076+
1077+
if (any(vnMinRange < 1) || any(vnMaxRange > oStack.vnDataSize(~vbIsColon)))
1078+
error('TIFFStack:badsubscript', ...
1079+
'*** TIFFStack: Index exceeds stack dimensions.');
1080+
end
1081+
1082+
% - Handle linear or subscript indexing
1083+
if (~bLinearIndexing)
1084+
1085+
try
1086+
% - Just read using MappedTensor
1087+
tfData = oStack.TIF(cIndices{1:end});
1088+
1089+
catch mErr
1090+
% - Record error state
1091+
base_ME = MException('TIFFStack:ReadError', ...
1092+
'*** TIFFStack: Could not read data from image file.');
1093+
new_ME = addCause(base_ME, mErr);
1094+
throw(new_ME);
1095+
end
1096+
1097+
else
1098+
% -- Linear indexing
1099+
1100+
% - Convert frame indices back to stack-linear
1101+
vnStackLinearIndices = sub2ind(oStack.vnDataSize, cIndices{:});
1102+
1103+
% - Loop over images in stack and extract required frames
1104+
try
1105+
% - Just read using MappedTensor
1106+
tfData = oStack.TIF(vnStackLinearIndices);
1107+
1108+
catch mErr
1109+
% - Record error state
1110+
base_ME = MException('TIFFStack:ReadError', ...
1111+
'*** TIFFStack: Could not read data from image file.');
1112+
new_ME = addCause(base_ME, mErr);
1113+
throw(new_ME);
1114+
end
1115+
end
1116+
1117+
% - Invert data if requested
1118+
if (oStack.bInvert)
1119+
tfData = oStack.sImageInfo(1).MaxSampleValue(1) - (tfData - oStack.sImageInfo(1).MinSampleValue(1));
1120+
end
1121+
end
1122+
1123+
9871124
% GetLinearIndicesForRefs - FUNCTION Convert a set of multi-dimensional indices directly into linear indices
9881125
function [vnLinearIndices, vnDimRefSizes] = GetLinearIndicesForRefs(cRefs, vnLims, hRepSumFunc)
9891126

@@ -1299,6 +1436,7 @@ function TS_set_directory_pre2014(tlStack, nDirectory) %#ok<DEFNU>
12991436
try
13001437
fid = fopen(strFile);
13011438
strFile = fopen(fid);
1439+
fclose(fid);
13021440

13031441
[strDir, strName, strExt] = fileparts(strFile);
13041442

@@ -1311,6 +1449,11 @@ function TS_set_directory_pre2014(tlStack, nDirectory) %#ok<DEFNU>
13111449
end
13121450

13131451
catch mErr
1452+
% - Close file id, if necessary
1453+
if (ismember(fid, fopen('all')))
1454+
fclose(fid);
1455+
end
1456+
13141457
% - Record error state
13151458
base_ME = MException('TIFFStack:ReadError', ...
13161459
'*** TIFFStack: Could not open file [%s].', strFile);
@@ -1324,8 +1467,6 @@ function TS_set_directory_pre2014(tlStack, nDirectory) %#ok<DEFNU>
13241467
bIsColon = ischar(ref) && isequal(ref, ':');
13251468
end
13261469

1327-
% --- END of TIFFStack.m ---
1328-
13291470
%% -- MEX-handling functions
13301471

13311472
function [hRepSumFunc] = GetMexFunctionHandles
@@ -1357,3 +1498,75 @@ function TS_set_directory_pre2014(tlStack, nDirectory) %#ok<DEFNU>
13571498
hRepSumFunc = @mapped_tensor_repsum_nomex;
13581499
end
13591500
end
1501+
1502+
%% -- ImageJ helper functions
1503+
1504+
function [bIsImageJBigStack, bIsImageJHyperStack, vnStackDims, vnInterleavedFrameDims] = IsImageJBigStack(sInfo)
1505+
1506+
% - Set up default return arguments
1507+
bIsImageJBigStack = false;
1508+
bIsImageJHyperStack = false;
1509+
vnStackDims = [];
1510+
vnInterleavedFrameDims = [];
1511+
1512+
% - Check for ImageDescription field
1513+
if (~isfield(sInfo, 'ImageDescription'))
1514+
return;
1515+
end
1516+
1517+
% - Get image description
1518+
strImageDesc = sInfo(1).ImageDescription;
1519+
1520+
% - Look for ImageJ version information
1521+
strImageJVer = sscanf(strImageDesc(strfind(strImageDesc, 'ImageJ='):end), 'ImageJ=%s');
1522+
1523+
% - Look for stack size information
1524+
if (~isempty(strImageJVer))
1525+
nNumImages = sscanf(strImageDesc(strfind(strImageDesc, 'images='):end), 'images=%d');
1526+
1527+
% - Does ImageJ report a greater number of images than sInfo?
1528+
if (~isempty(nNumImages) && (numel(sInfo) ~= nNumImages))
1529+
bIsImageJBigStack = true;
1530+
end
1531+
1532+
% - Is this a hyperstack?
1533+
strHyperStack = sscanf(strImageDesc(strfind(strImageDesc, 'hyperstack='):end), 'hyperstack=%s');
1534+
1535+
if (strcmpi(strHyperStack, 'true'))
1536+
bIsImageJHyperStack = true;
1537+
1538+
% - Extract information about the stack size for a hyperstack
1539+
nNumChannels = sscanf(strImageDesc(strfind(strImageDesc, 'channels='):end), 'channels=%d');
1540+
nNumSlices = sscanf(strImageDesc(strfind(strImageDesc, 'slices='):end), 'slices=%d');
1541+
nNumFrames = sscanf(strImageDesc(strfind(strImageDesc, 'frames='):end), 'frames=%d');
1542+
1543+
% - Deinterleave stack
1544+
vnStackDims = [sInfo(1).Height sInfo(1).Width nNumFrames*nNumSlices*nNumChannels 1];
1545+
vnInterleavedFrameDims = [nNumChannels nNumSlices nNumFrames];
1546+
1547+
else
1548+
% - Extract information about the stack size for a fake big stack
1549+
vnStackDims = [sInfo(1).Height sInfo(1).Width nNumImages sInfo(1).SamplesPerPixel];
1550+
vnInterleavedFrameDims = [];
1551+
end
1552+
end
1553+
end
1554+
1555+
function [mtHandle, bUseMappedTensor, strDataClass] = OpenImageJBigStack(oStack, vnStackDims)
1556+
bUseMappedTensor = (exist('MappedTensor', 'class') == 8);
1557+
mtHandle = [];
1558+
strDataClass = [];
1559+
1560+
if (bUseMappedTensor)
1561+
% - Use tiffread to get file information
1562+
[TIF, HEADER] = tiffread31_header(oStack.strFilename);
1563+
1564+
% - Use MappedTensor to open the file
1565+
strDataClass = TIF.classname;
1566+
mtHandle = MappedTensor(oStack.strFilename, vnStackDims, ...
1567+
'MachineFormat', TIF.ByteOrder, 'Class', strDataClass, 'HeaderBytes', HEADER.StripOffsets);
1568+
end
1569+
end
1570+
1571+
% --- END of TIFFStack.m ---
1572+

@TIFFStack/private/tiffread31_header.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@
308308
case 2
309309
TIF.classname = sprintf('int%i', TIF.BitsPerSample(1));
310310
case 3
311-
if ( TIF.BitsPerSample(plane_nb) == 32 )
311+
if (TIF.BitsPerSample(1) == 32 )
312312
TIF.classname = 'single';
313313
else
314314
TIF.classname = 'double';

0 commit comments

Comments
 (0)