Code covered by the BSD License  

Highlights from
TIFFStack

image thumbnail
from TIFFStack by Dylan Muir
Access a TIFF stack as a memory-mapped tensor. Handles a large range of internal TIFF formats.

TIFFStack
% TIFFStack - Manipulate a TIFF file like a tensor
% 
% Usage: tsStack = TIFFStack(strFilename <, bInvert>)
% 
% A TIFFStack object behaves like a read-only memory mapped TIF file.  The
% entire image stack is treated as a matlab tensor.  Each frame of the file must
% have the same dimensions.  Reading the image data is optimised to the extent
% possible; the header information is only read once.
% 
% This class uses a modified version of tiffread [1, 2] to read data.  Code is
% included (but disabled) to use the matlab imread function, but this function
% returns invalid data for some TIFF formats.
% 
% Construction:
% 
% >> tsStack = TIFFStack('test.tiff');       % Construct a TIFF stack associated with a file
% 
% >> tsStack = TIFFStack('test.tiff', true); % Indicate that the image data should be inverted
% 
% tsStack = 
% 
%   TIFFStack handle
% 
%   Properties:
%          bInvert: 0
%      strFilename: [1x9 char]
%       sImageInfo: [5x1 struct]
%     strDataClass: 'uint16'
% 
% Usage:
% 
% >> tsStack(:, :, 3);     % Retrieve the 3rd frame of the stack, all planes
% 
% >> tsStack(:, :, 1, 3);  % Retrieve the 3rd plane of the 1st frame
% 
% >> size(tsStack)         % Find the size of the stack (rows, cols, frames, planes per pixel)
% 
% ans =
% 
%    128   128     5     1
% 
% >> tsStack(4);           % Linear indexing is supported
% 
% >> tsStack.bInvert = true;  % Turn on data inversion
% 
% References:
% [1] Francois Nedelec, Thomas Surrey and A.C. Maggs. Physical Review Letters
%        86: 3192-3195; 2001. DOI: 10.1103/PhysRevLett.86.3192
% 
% [2] http://www.embl.de/~nedelec/

% Author: Dylan Muir <muir@hifo.uzh.ch>
% Created: 28th June, 2011

classdef TIFFStack < handle
   properties
      bInvert;             % - A boolean flag that determines whether or not the image data will be inverted
   end
   
   properties (SetAccess = private)
      strFilename = [];    % - The name of the TIFF file on disk
      sImageInfo;          % - The TIFF header information
      strDataClass;        % - The matlab class in which data will be returned
   end
   
   properties (SetAccess = private, GetAccess = private)
      vnStackSize;
      TIF;                 % \_ Cached header infor for tiffread29 speedups
      HEADER;              % /
   end
   
   methods
      % TIFFStack - CONSTRUCTOR
      function oStack = TIFFStack(strFilename, bInvert)
         % - Check for inversion flag
         if (~exist('bInvert', 'var'))
            bInvert = false;
         end
         oStack.bInvert = bInvert;
         
         % - See if filename exists
         if (~exist(strFilename, 'file'))
            error('TIFFStack:InvalidFile', ...
                  '*** TIFFStack: File [%s] does not exist.', strFilename);
         end
         
         % - Assign absolute file path to stack
         oStack.strFilename = get_full_file_path(strFilename);
         
         % - Get image information
         try
            % - Read and save image information
            sInfo = imfinfo(strFilename);
            oStack.sImageInfo = sInfo;

            % - Read TIFF header for tiffread29
            [oStack.TIF, oStack.HEADER] = tiffread29_header(strFilename);
            
            % - Use imread to get the data class for this tiff
            % fPixel = imread(strFilename, 'TIFF', 1, 'PixelRegion', {[1 1], [1 1]});
            % oStack.strDataClass = class(fPixel);
            
            % - Use tiffread29 to get the data class for this tiff
            fPixel = tiffread29_readimage(oStack.TIF, oStack.HEADER, 1);
            fPixel = fPixel(1, 1, :);
            oStack.strDataClass = class(fPixel);

            % - Record stack size
            oStack.vnStackSize = [sInfo(1).Height sInfo(1).Width numel(sInfo) numel(fPixel)];
            
         catch mErr
            base_ME = MException('TIFFStack:InvalidFile', ...
                  '*** TIFFStack: Could not open file [%s].', strFilename);
            new_ME = addCause(base_ME, mErr);
            throw(new_ME);
         end
      end
      
      % delete - DESTRUCTOR
      function delete(oStack)
         % - Close the TIFF file, if opened by tiffread29_header
         if (isfield(oStack.TIF, 'file'))
            fclose(oStack.TIF.file);
         end
      end

%% --- Overloaded subsref

      function [tfData] = subsref(oStack, S)
         switch S(1).type
            case '()'
               nNumDims = numel(S.subs);
               nNumStackDims = numel(oStack.vnStackSize);
               
               % - Check dimensionality and trailing dimensions
               if (nNumDims == 1)
                  % - Translate from linear refs to indices
                  nNumDims = nNumStackDims;
                  
                  % - Translate colon indexing
                  if (isequal(S.subs{1}, ':'))
                     S.subs{1} = (1:prod(oStack.vnStackSize))';
                  end
                  
                  % - Get equivalent subscripted indexes
                  [S.subs{1:nNumDims}] = ind2sub(oStack.vnStackSize, S.subs{1});
                  
               elseif (nNumDims < nNumStackDims)
                  % - Assume trailing references are ':"
                  S.subs(nNumDims+1:nNumStackDims) = {':'};
                  
               elseif (nNumDims > nNumStackDims)
                  % - Check for non-colon references
                  vbNonColon = cellfun(@(c)(~ischar(c) | ~isequal(c, ':')), S.subs);
                  
                  % - Check only trailing dimensions
                  vbNonColon(1:nNumStackDims) = false;
                  
                  % - Check trailing dimensions for non-'1' indices
                  if (any(cellfun(@(c)(~isequal(c, 1)), S.subs(vbNonColon))))
                     % - This is an error
                     error('TIFFStack:badsubscript', ...
                        '*** TIFFStack: Index exceeds stack dimensions.');
                  end
                  
                  % - Only keep relevant dimensions
                  S.subs = S.subs(1:nNumStackDims);
               end
               
               % - Access stack
               tfData = TS_read_data_tiffread(oStack, S.subs);
               
               % - Reshape return data to concatenate trailing dimensions (just as
               % matlab does)
               if (nNumDims < nNumStackDims)
                  cnSize = num2cell(size(tfData));
                  tfData = reshape(tfData, cnSize{1:nNumDims-1}, []);
               end
               
            case '.'
               tfData = builtin('subsref', oStack, S);
               
            otherwise
               error('TIFFStack:InvalidReferencing', ...
                     '*** TIFFStack: Only ''()'' referencing is supported by TIFFStacks.');
         end
      end
      
%% --- Overloaded size
      function [varargout] = size(oStack, vnDimensions)
         % - Return the size of the stack
         vnSize = oStack.vnStackSize;
         
         % - Return specific dimension(s)
         if (exist('vnDimensions', 'var'))
            if (~isnumeric(vnDimensions))
               error('TIFFStack:dimensionMustBePositiveInteger', ...
                  '*** TIFFStack: Dimensions argument must be a positive integer within indexing range.');
            end
            
            % - Return the specified dimension(s)
            vnSize = vnSize(vnDimensions);
         end
         
         % - Handle differing number of size dimensions and number of output
         % arguments
         nNumArgout = max(1, nargout);
         
         if (nNumArgout == 1)
            % - Single return argument -- return entire size vector
            varargout{1} = vnSize;
            
         elseif (nNumArgout <= numel(vnSize))
            % - Several return arguments -- return single size vector elements,
            % with the remaining elements grouped in the last value
            varargout(1:nNumArgout-1) = num2cell(vnSize(1:nNumArgout-1));
            varargout{nNumArgout} = prod(vnSize(nNumArgout:end));
            
         else %(nNumArgout > numel(vnSize))
            % - Output all size elements
            varargout(1:numel(vnSize)) = num2cell(vnSize);
            
            % - Deal out trailing dimensions as '1'
            varargout(numel(vnSize)+1:nNumArgout) = {1};
         end
      end
      
%% --- Property accessors

      % set.bInvert - SETTER method for 'bInvert'
      function set.bInvert(oStack, bInvert)
         % - Check contents
         if (~islogical(bInvert) || ~isscalar(bInvert))
            error('TIFFStack:invalidArgument', ...
                  '*** TIFFStack/set.bInvert: ''bInvert'' must be a logical scalar.');
         else
            % - Assign bInvert value
            oStack.bInvert = bInvert;
         end
      end
      
   end
end

%% --- Helper functions ---

% TS_read_data_imread - FUNCTION Read the requested pixels from the TIFF file (using imread)
%
% Usage: [tfData] = TS_read_data_imread(oStack, cIndices)
%
% 'oStack' is a TIFFStack.  'cIndices' are the indices passed in from subsref.
% Colon indexing will be converted to full range indexing.  Reading is optimsed,
% by only reading pixels in a minimal-size window surrouding the requested
% pixels.  cIndices is a cell array with the format {rows, cols, frames,
% slices}.  Slices are RGB or CMYK or so on.

function [tfData] = TS_read_data_imread(oStack, cIndices) %#ok<DEFNU>
   % - Convert colon indexing
   vbIsColon = cellfun(@(c)(isequal(c, ':')), cIndices);
   
   for (nColonDim = find(vbIsColon))
      cIndices{nColonDim} = 1:oStack.vnStackSize(nColonDim);
   end
      
   % - Check ranges
   vnMinRange = cellfun(@(c)(min(c)), cIndices);
   vnMaxRange = cellfun(@(c)(max(c)), cIndices);
   
   if (any(vnMinRange < 1) || any(vnMaxRange > oStack.vnStackSize))
      error('TIFFStack:badsubscript', ...
            '*** TIFFStack: Index exceeds stack dimensions.');
   end
   
   % - Allocate large tensor
   vnBlockSize = vnMaxRange(1:2) - vnMinRange(1:2) + [1 1];
   vnBlockSize(3) = numel(cIndices{3});
   vnBlockSize(4) = oStack.vnStackSize(4);
   tfDataBlock = zeros(vnBlockSize, oStack.strDataClass);
   cBlock = {[vnMinRange(1) vnMaxRange(1)] [vnMinRange(2) vnMaxRange(2)]};
   
   % - Loop over frames to read data block
   try
      % - Disable warnings
      wOld = warning('off', 'MATLAB:rtifc:notPhotoTransformed');
      
      for (nFrame = 1:numel(cIndices{3})) %#ok<FORPF>
         tfDataBlock(:, :, nFrame, :) = imread(oStack.strFilename, 'TIFF', cIndices{3}(nFrame), 'PixelRegion', cBlock);
      end
      
      % - Re-enable warnings
      warning(wOld);
      
   catch mErr
      % - Record error state
      base_ME = MException('TIFFStack:ReadError', ...
                           '*** TIFFStack: Could not read data from image file.');
      new_ME = addCause(base_ME, mErr);
      throw(new_ME);
   end
   
   % - Do we need to resample the data block?
   bResample = any(~vbIsColon(1:3));
   if (bResample)
      cIndices{1} = cIndices{1} - vnMinRange(1) + 1;
      cIndices{2} = cIndices{2} - vnMinRange(2) + 1;
      tfData = tfDataBlock(cIndices{1}, cIndices{2}, :, cIndices{4});
   else
      tfData = tfDataBlock;
   end
   
   % - Invert data if requested
   if (oStack.bInvert)
      tfData = oStack.sImageInfo(1).MaxSampleValue - (tfData - oStack.sImageInfo(1).MinSampleValue);
   end
end


% TS_read_data_tiffread - FUNCTION Read the requested pixels from the TIFF file (using tiffread29)
%
% Usage: [tfData] = TS_read_data_imread(oStack, cIndices)
%
% 'oStack' is a TIFFStack.  'cIndices' are the indices passed in from subsref.
% Colon indexing will be converted to full range indexing.  cIndices is a cell
% array with the format {rows, cols, frames, slices}.  Slices are RGB or CMYK
% or so on.

function [tfData] = TS_read_data_tiffread(oStack, cIndices)
   % - Convert colon indexing
   vbIsColon = cellfun(@(c)(ischar(c) & isequal(c, ':')), cIndices);
   
   for (nColonDim = find(vbIsColon))
      cIndices{nColonDim} = 1:oStack.vnStackSize(nColonDim);
   end
      
   % - Check ranges
   vnMinRange = cellfun(@(c)(min(c)), cIndices);
   vnMaxRange = cellfun(@(c)(max(c)), cIndices);
   
   if (any(vnMinRange < 1) || any(vnMaxRange > oStack.vnStackSize))
      error('TIFFStack:badsubscript', ...
            '*** TIFFStack: Index exceeds stack dimensions.');
   end
   
   % - Allocate large tensor
   vnBlockSize = oStack.vnStackSize(1:2);
   vnBlockSize(3) = numel(cIndices{3});
   vnBlockSize(4) = oStack.vnStackSize(4);
   tfDataBlock = zeros(vnBlockSize, oStack.strDataClass);
   
   % - Read data block
   try
      tfDataBlock = tiffread29_readimage(oStack.TIF, oStack.HEADER, cIndices{3});
           
   catch mErr
      % - Record error state
      base_ME = MException('TIFFStack:ReadError', ...
                           '*** TIFFStack: Could not read data from image file.');
      new_ME = addCause(base_ME, mErr);
      throw(new_ME);
   end
   
   % - Do we need to resample the data block?
   bResample = any(~vbIsColon(1:3));
   if (bResample)
      tfData = tfDataBlock(cIndices{1}, cIndices{2}, :, cIndices{4});
   else
      tfData = tfDataBlock;
   end
   
   % - Invert data if requested
   if (oStack.bInvert)
      tfData = oStack.sImageInfo(1).MaxSampleValue - (tfData - oStack.sImageInfo(1).MinSampleValue);
   end
end

% get_full_file_path - FUNCTION Calculate the absolute path to a given (possibly relative) filename
%
% Usage: strFullPath = get_full_file_path(strFile)
%
% 'strFile' is a filename, which may include relative path elements.  The file
% does not have to exist.
%
% 'strFullPath' will be the absolute path to the file indicated in 'strFile'.

function strFullPath = get_full_file_path(strFile)

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

   if (isempty(strDir))
      strDir = '.';
   end

   strFullDirPath = cd(cd(strDir));
   strFullPath = fullfile(strFullDirPath, [strName strExt]);
end

Contact us