Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
b2faf8451a
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
471 lines (429 sloc) 18.5 KB
classdef TiffWriter < imageIO.ImageIO
%TIFFWRITER Wrapper for Tiff interface, based on libTIFF library
% This class is a single wrapper around libTiff class and the mex files
% developed to perform fast writing of multiplane Tiff files, adapted
% to conform to the structure of the imageIO library. This class does
% not implement the whole TIFF standard but only a subset. Furthermore,
% mex files exist only for Windows and Linux, 64 bit versions. When no
% fast method is available (e.g. for logicals, float 32 bit or BigTiff)
% the class reverts to the Matlab slow implementation.
%
% Author: Stefano.Masneri@brain.mpge.de
% Date: 29.07.2016
% SEE ALSO: imageIO.TiffReader, Tiff, imageIO.TiffWriter.TiffWriter,
% imageIO.TiffWriter.writeData
properties
resolution; % 2 elements vector specifying X and Y resolution
resolutionUnit; % unit of measurement for resolution (none, inch, centimeter)
colormap; % colormap used. Empty array if none
compression; % compression scheme used
isRGB; % true if the image is RGB. The user has to specify it
% to distinguish between RGB images or grayscale images
% with 3 stacks. Default is false
isBig; % true if the data to write is bigger than 4Gb
bitsPerSample; % number of bits used to represent one pixel
checkExistence; % before writing data, checks if the file exists
end
methods
function obj = TiffWriter(filename, varargin)
%TIFFWRITER Constructor of the class
%The constructor calls the constructor of the superclass, and then
%parses the input in order to get the information required to write the
%data. The constructor requires a filename parameter, can have a map as
%optional parameter. All other parameters are stored as Name-Value
%pairs. This (horrible) syntax is used to have consistency with the way
%imwrite works
% SYNTAX:
% obj = imageIO.TiffWriter( filename, varargin )
% filename is the only mandatory parameter. If no other
% parameters are specified, the function uses the default ones.
% obj = imageIO.TiffWriter( map, filename, varargin )
% alternative where the user specifies a colormap as the first
% argument.
% INPUT:
% filename the filename used to save data on disk - MANDATORY
% map: the colormap used - OPTIONAL
% NAME-VALUE INPUT PARAMETERS:
% compression compression used when saved images. default is 'lzw',
% other values allowed are 'none', 'lzw', 'deflate', 'packbits'
% isRGB explicitly specifies if the data should be saved as an RGB color
% image. Default is false.
% checkExistence (true/false) specifies if checks should be performed for the existance
% of the file to a) warn if it is overwritten and b) create it if
% writemode write is used on a nonexisting file.
% Can be very timeconsuming on large folder and can therefore be turned
% off. Default true.
% isBig boolean specifying whether the final files will be bigger than 4
% Gb. Default false. This parameter is required when the user wants
% to write a file in several steps and there is no other way for
% the class to know beforehand how big the files will be. Standard
% tiff can write files of up to 4Gb
% resolution A two-element vector containing the XResolution and YResolution,
% or a scalar indicating both resolutions; the default value is 72
% resolutionUnit Specifies the unit used to specify the resolution
% parameter. Can be 'inch', 'centimeter', 'cm', 'millimeter', 'mm', 'micrometer',
% 'um', or 'unknown' (the default)
% OUTPUT:
% obj the constructed object
%SEE ALSO imageIO.ImageIO.ImageIO, imageIO.TiffWriter.parseArgs,
% imageIO.TiffWriter.write
% Fix filename if is not with tif extension
[~, ~, ext] = fileparts(filename);
if ~(strcmpi(ext, '.tif') || strcmpi(ext, '.tiff'))
warning('TiffWriter: Using incorrect extension! Appending ''.tif'' to filename')
filename = [filename '.tif'];
end
% Must call explictily because we pass one argument
obj = obj@imageIO.ImageIO(filename);
% parse input
p = inputParser;
%filename is parsed in the superclass method
p.addOptional('map', [], @(x) ismatrix(x) && isnumeric(x) && ...
all(x(:)) >= 0 && size(x, 2) == 3);
p.addParameter('compression', 'lzw', @(x) any(strcmp(x, {'none', 'lzw', ...
'deflate', 'packbits'} ) ) );
p.addParameter('isRGB', false, @islogical);
p.addParameter('checkExistence', true, @islogical);
p.addParameter('isBig', false, @islogical);
p.addParameter('resolution', 72, @(x) isvector(x) && length(x) <= 2 && ...
isinteger(uint32(x) ) );
p.addParameter('resolutionUnit', 'unknown', @(x) any(strcmp(x, {'inch', 'centimeter', ...
'cm', 'millimeter', 'mm', 'micrometer', 'um', 'unknown'} ) ) );
p.parse(varargin{:});
% set properties
obj.isBig = p.Results.isBig;
obj.isRGB = p.Results.isRGB;
obj.checkExistence = p.Results.checkExistence;
obj.colormap = p.Results.map;
obj.resolution = uint32(p.Results.resolution);
if isscalar(obj.resolution)
obj.resolution = [obj.resolution obj.resolution];
end
% Specify resolution according to resolution unit
if strcmp(p.Results.resolutionUnit, 'unknown')
obj.resolutionUnit = int16(1); %RESUNIT_INCH in Tiff standard
elseif strcmp(p.Results.resolutionUnit, 'inch')
obj.resolutionUnit = int16(2); %RESUNIT_INCH in Tiff standard
else
obj.resolutionUnit = int16(3); %%RESUNIT_CENTIMETER in Tiff standard
if any(strcmp(p.Results.resolutionUnit, {'millimeter', 'mm'}));
obj.resolutionUnit = 10 * obj.resolutionUnit;
elseif any(strcmp(p.Results.resolutionUnit, {'micrometer', 'um'}));
obj.resolutionUnit = 10000 * obj.resolutionUnit;
end % do nothing is unit is centimeter
end
% Check compression
switch p.Results.compression
case 'none'
obj.compression = Tiff.Compression.None;
case 'lzw'
obj.compression = Tiff.Compression.LZW;
case 'deflate'
obj.compression = Tiff.Compression.Deflate;
otherwise %paranoia
obj.compression = Tiff.Compression.PackBits;
end
obj.compression = uint16(obj.compression);
% Check map
% Tiff library wants colormap to be a vector of length 256 (for each color
% channel). So we interpolate if the length is shorter than that
if ~isempty(obj.colormap) && length(obj.colormap) ~= 256
len = size(obj.colormap, 1);
tempCM = zeros(256, 3);
for k = 1:3
tempCM(:,k) = interp1(1:len, double(obj.colormap(:,k)), linspace(1, len, 256));
end
obj.colormap = tempCM;
end
% Matlab always uses colormaps between 0 and 1, type double. TIFF
% specification instead wants uint16. So if the data is double and less
% than one we rescale. If it is another type we convert to uint16
if ~isempty(obj.colormap)
if isa(obj.colormap, 'double') && max(obj.colormap(:)) <= 1
obj.colormap = uint16(65536 * obj.colormap);
elseif ~isa(obj.colormap, 'uint16')
obj.colormap = uint16(obj.colormap);
end
end
end
function write(obj, data, varargin)
%WRITE Write data on file
%Writes data on the file linked to the TiffWriter object. Apart from
%the mandatoy parameter data, all other parameters are passed as
%Name-Value pairs
% INPUT:
% data: the data to write
% writeMode: file opening mode. Default is 'create' (to create a new file).
% Other accepted values are 'append', to append to a file previously
% closed, or 'write' to add data to an already opened file. PLEASE NOTE
% that calling 'append' on a file which was already opened will close
% the file and then re-open it, losing the advantages of fast tiff
% writing. If the file is already opoened the correct behaviour is to
% use the 'write' mode.
% close: (true/false): The file will only be closed if close is set to true
% By default close is true. If false, file is left open for further write
% operations. In this case the user has to call obj.close()
% once he is done with writing to this file.
% numImages total number of images that should be written to file
% EXAMPLES:
% tw = imageIO.TiffWriter('test.tiff');
% data = uint8(ones(1024, 512, 50));
%
% tw.write( data ) writes all the 50 images of data on
% tw.write( data, 'numImages', 20 ) writes only the first 20 images
% tw.write( data, 'numImages', 70 ) writes all the 50
% images of data and issue a warning, because the specified number
% of images is greater than the number of images in
% tw.writeData( data, 'writeMode', 'a') appends data
% For fast writing of multipage tiff on the same file:
% tw.write( data, 'close', false );
% tw.write( newdata, 'writemode', 'write' )
% tw.write( otherdata, 'writemode', 'write' )
% tw.close();
% SEE ALSO:
% imwrite, imageIO.TiffWriter.TiffWriter
% parse input
p = inputParser;
p.addRequired('data', @(x) isnumeric(x) || islogical(x));
p.addParameter('numImages', 0, @(x) isinteger(uint32(x) ) );
p.addParameter('writeMode', 'create', @(x) any(strcmp(x, {'write', ...
'create', 'append', 'w', 'c', 'a'} ) ) );
p.addParameter('close', true, @islogical);
p.parse(data, varargin{:});
% set values used for writing data
numImages = p.Results.numImages;
writeMode = p.Results.writeMode;
if strcmp( p.Results.writeMode, 'w')
writeMode = 'write';
elseif strcmp( p.Results.writeMode, 'c')
writeMode = 'create';
elseif strcmp( p.Results.writeMode, 'a')
writeMode = 'append';
end
numDims = ndims(data);
dataSize = size(data);
% number of images to write
if numDims == 2
obj.stacks = 1;
elseif numDims == 3 && obj.isRGB && dataSize(3) == 3
obj.stacks = 1;
else
tiffImgs = size(data, numDims);
if numImages > 0
if tiffImgs < numImages
obj.stacks = tiffImgs;
warning('Tiffwriter.writeData: Trying to write more images than available!');
else
obj.stacks = numImages;
end
else
obj.stacks = tiffImgs;
end
end
% check datatype
switch class(data)
case {'uint8', 'int8'}
obj.bitsPerSample = 8;
case {'uint16', 'int16'}
obj.bitsPerSample = 16;
case {'uint32', 'int32', 'single'}
obj.bitsPerSample = 32;
case {'double'}
obj.bitsPerSample = 64;
case {'logical'}
obj.bitsPerSample = 1;
otherwise
error('ERROR: Unsupported data type')
end
%check existence, if required
if obj.checkExistence
fileExists = exist(obj.fileFullPath, 'file');
if fileExists && strcmp(writeMode, 'create')
warning('Tiffwriter.writeData: File already exists, will be overwritten');
end
end
% Check if we can use fast method
if ~obj.isBig % if false, recheck!
obj.isBig = (obj.bitsPerSample/8 * obj.stacks * dataSize(1) * dataSize(2)) > 4294967295;
end
if ~islogical(data) && ~isfloat(data) && ~obj.isBig
%doesn't work with logicals / floating points / > 4GB
try
obj.writeFast(writeMode, close, data);
catch ME
if strcmpi(ME.identifier,'MATLAB:invalidMEXFile')
warning('You probably do not have the Microsoft C++ runtime installed. Install it from https://www.microsoft.com/en-us/download/details.aspx?id=48145')
end
multitiff('close');
error('TiffWriter.writeData: Cannot save file. %s', ME.message)
end
else
try
obj.writeSlow(writeMode, data);
catch ME
error('TiffWriter.writeData: Cannot save file. %s', ME.message)
end
end
end
function data = read(obj, varargin)
% Do nothing, it's here because abstract in superclass
end
end
methods (Access = protected)
function writeFast(obj, writeMode, close, data)
%WRITEFAST Write data to file using the mex files which directly access
%the tiff library. It enhances speed by not needing to open and close
%the tiff file when writing new directories, thus avoiding the O(n^2) time
%required to navigate the file.
if strcmp(writeMode, 'create') || strcmp(writeMode, 'append')
multitiff('create', obj.fileFullPath);
end
numDims = ndims(data);
if numDims == 2
if isempty(obj.colormap)
multitiff('write', data', [], obj.compression, obj.resolution, ...
obj.resolutionUnit);
else
multitiff('write', data', obj.colormap, obj.compression, ...
obj.resolution, obj.resolutionUnit);
end
elseif numDims == 3 && obj.isRGB && dataSize(3) == 3
dataToWrite = permute(data,[3 2 1]); %different ordering compared to Matlab
if ~isempty(obj.colormap)
warning('TiffWriter.writeFast: Colormaps are used only with single channel data!')
end
multitiff('write', dataToWrite, [], obj.compression, obj.resolution, ...
obj.resolutionUnit);
else
for k = 1:obj.stacks
if numDims == 3
if isempty(obj.colormap)
multitiff('write', squeeze(data(:,:,k))', [], obj.compression, ...
obj.resolution, obj.resolutionUnit);
else
multitiff('write', squeeze(data(:,:,k))', obj.colormap, obj.compression, ...
obj.resolution, obj.resolutionUnit);
end
else
dataToWrite = permute( squeeze(data(:,:,:,k)), [3 2 1] );
if ~isempty(obj.colormap)
warning('TiffWriter.writeFast: Colormaps are used only with single channel data!')
end
multitiff('write', dataToWrite, [], obj.compression, ...
obj.resolution, obj.resolutionUnit);
end
end
end
if close
multitiff('close');
end
end
function writeSlow(obj, writeMode, data)
%WRITESLOW Write data to file using Matlab interface accessing the Tiff
%library. This method is slower because it doesn't take advantage of
%the mex files keeping track of the last directory written. It is
%nonetheless the only method usable for some datatypes.
numDims = ndims(data);
dataSize = size(data);
% we must setup the Tiff tags
% RGB or greyscale?
if numDims == 2
samplesPerPixel = 1;
tagstruct.Photometric = Tiff.Photometric.MinIsBlack;
elseif numDims == 3
if dataSize(3) == 3 && p.Results.isRGB % it's a single RGB
samplesPerPixel = 3;
tagstruct.Photometric = Tiff.Photometric.RGB;
else %it is a stack of 3 grayscale images
samplesPerPixel = 1;
tagstruct.Photometric = Tiff.Photometric.MinIsBlack;
end
elseif numDims == 4
samplesPerPixel = 3;
tagstruct.Photometric = Tiff.Photometric.RGB;
else
msgID = 'TiffWriter.writeSlow.incorrectData';
msg = 'Incorrect data size. data must be either 2D, 3D or 4D';
exc = MException(msgID, msg);
throw(exc);
end
% setup tiff tag
tagstruct.ImageLength = size(data,1);
tagstruct.ImageWidth = size(data,2);
tagstruct.BitsPerSample = obj.bitsPerSample;
tagstruct.SamplesPerPixel = samplesPerPixel;
tagstruct.RowsPerStrip = size(data,2);
tagstruct.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky;
tagstruct.Software = 'MATLAB';
tagstruct.XResolution = double(obj.resolution(1));
tagstruct.YResolution = double(obj.resolution(2));
tagstruct.Compression = double(obj.compression);
switch class(data)
case {'uint8', 'uint16', 'uint32', 'logical'}
tagstruct.SampleFormat = Tiff.SampleFormat.UInt;
case {'int8', 'int16', 'int32'}
tagstruct.SampleFormat = Tiff.SampleFormat.Int;
case {'single', 'double'}
tagstruct.SampleFormat = Tiff.SampleFormat.IEEEFP;
otherwise
msgID = 'TiffWriter.writeSlow.unsupportedDatasize';
msg = 'Unsupported datatype';
exc = MException(msgID, msg);
throw(exc);
end
%from create, append, write to w / a
if strcmp(writeMode, 'append')
wrtMode = 'a';
elseif obj.isBig
wrtMode = 'w8';
else
wrtMode = 'w';
end
try
t = Tiff(obj.fileFullPath, wrtMode);
% Actually apply this tag to the first image
t.setTag(tagstruct);
% Write the first image
if numDims == 2 || ( numDims == 3 && obj.isRGB && dataSize(3) == 3)
t.write(data);
elseif numDims == 3
t.write(data(:,:,1));
else %already checked that numDim is either 3 or 4
t.write(data(:,:,:,1));
end
% For all further images
for k = 2:obj.stacks
% Create a new image inside the file
t.writeDirectory();
% Apply the tag for the new sub image
t.setTag(tagstruct);
% Write the image
if numDims == 3
write(t,data(:,:,k));
else %already checked that numDim is either 3 or 4
write(t,data(:,:,:,k));
end
end
% Close the Tiff file
t.close()
catch ME
t.close()
throw(ME);
end
end
function obj = readMetadata(obj)
% Do nothing, it's here because abstarct in superclass
end
end
methods (Static = true)
function delete()
%DELETE Close object instances.
%Close performs the cleanup and release of the instantiated object.
%This method is static because the fast method for writing Tiff
%requires that at most one file is open at the same time, so calling
%the close will just close the instance opened, indipendently from
%what the user has been doing
multitiff.close();
end
end
end