MATLAB: How to update Tiff object in ImageAdapter class

imageadapterMATLABmulti-page tifftiff

So, for various reasons (i.e. blockproc) I am trying to create a BigTIFF ImageAdapter class that also writes multiple images to the same file. However, these images are not all the same size – they're lower-resolution versions of the same image. Essentially, I'm trying to create a multi-resolution BigTIFF file, but it has to be done through an ImageAdapter/blockproc because the original image is simply too big.
The problem is that when I call TIFF's writeDirectory() function in my ImageAdapter, it seems to wipe out all information in the TIFF object stored in the class, and calls to TIFF's setTag() no longer work on the object (although getTag() does return the correct answer immediately after, but doesn't persist). This may be a simple case of me not understanding how to work with handle-type classes in Matlab, so please let me know if I'm doing something incorrectly:
classdef PagedBigTiffWriter < ImageAdapter
properties(GetAccess = public, SetAccess = private)
Filename;
TiffObject;
TileLength;
TileWidth;
end
methods
function obj = PagedBigTiffWriter(fname, imageLength, imageWidth, tileLength,...
tileWidth, bitsPerSample, compression, sampleFormat)
if(mod(tileLength,16)~=0 || mod(tileWidth,16)~=0)
error('bigTiffWriter:invalidTileSize',...
'Tile size must be a multiple of 16');
end
if strcmpi(sampleFormat,'IEEE floating point')
sFormat = Tiff.SampleFormat.IEEEFP;
elseif strcmpi(sampleFormat, 'integer')
sFormat = Tiff.SampleFormat.Int;
else
sFormat = Tiff.SampleFormat.UInt;
end
obj.Filename = fname;
obj.ImageSize = [imageLength, imageWidth, 1];
obj.TileLength = tileLength;
obj.TileWidth = tileWidth;
% Create the Tiff object.
obj.TiffObject = Tiff(obj.Filename, 'w8');
% Setup the tiff file properties
% See "Exporting Image Data and Metadata to TIFF files"
% https://www.mathworks.com/help/matlab/import_export/exporting-to-images.html
%
obj.TiffObject.setTag('ImageLength', obj.ImageSize(1));
obj.TiffObject.setTag('ImageWidth', obj.ImageSize(2));
obj.TiffObject.setTag('TileLength', obj.TileLength);
obj.TiffObject.setTag('TileWidth', obj.TileWidth);
obj.TiffObject.setTag('Photometric', Tiff.Photometric.MinIsBlack);
obj.TiffObject.setTag('BitsPerSample', bitsPerSample);
obj.TiffObject.setTag('SampleFormat', sFormat);
obj.TiffObject.setTag('SamplesPerPixel', 1);
obj.TiffObject.setTag('PlanarConfiguration', Tiff.PlanarConfiguration.Chunky);
obj.TiffObject.setTag('Compression', Tiff.Compression.(compression));
end
function [] = changePage(obj, imageSize, tileSize)
% increments to next directory, so next time we call blockproc
% it will write to the next page
obj.TiffObject.writeDirectory();
% now write out new metadata for the new page
obj.ImageSize = [imageSize(1), imageSize(2), 1]; %length first
obj.TileLength = tileSize(1);
obj.TileWidth = tileSize(2);
obj.TiffObject.setTag('ImageLength', obj.ImageSize(1)); % <- these do nothing, and obj.TiffObj is essentially blank now
obj.TiffObject.setTag('ImageWidth', obj.ImageSize(2));
obj.TiffObject.setTag('TileLength', obj.TileLength);
obj.TiffObject.setTag('TileWidth', obj.TileWidth);
% probably need rest of metadata from first page?
%obj.TiffObject.rewriteDirectory(); %not necessary, as haven't
%written meta to this directory before?
end
function [] = writeRegion(obj, region_start, region_data)
% Map region_start to a tile number.
tile_number = obj.TiffObject.computeTile(region_start);
% If region_data is greater than tile size, this function
% warns, else it will silently pads with 0s.
obj.TiffObject.writeEncodedTile(tile_number, region_data);
end
function data = readRegion(~,~,~) %#ok<STOUT>
% Not implemented.
error('bigTiffWriter:noReadSupport',...
'Read support is not implemented.');
end
function close(obj)
% Close the tiff file
obj.TiffObject.close();
end
end
end
In changePage(), the call to obj.TiffObject.writeDirectory() successfully changes the directory, but all that's left in obj.TiffObject is the file name and mode. The indicated calls to setTag do not change obj.TiffObject, (although calling getTag() returns the correct answer, printing out obj.TiffObject shows nothing). Why? Should I be doing this differently? I've heard of subdirectories in TIFF, which may actually be more appropriate for my multi-resolution files, but I couldn't figure out how to get that working. And I tried setting changePage to return obj, but that didn't work either.
I know from reading around that working with TIFFs through Matlab can be a pain, and I have seen the various file exchange options for TIFF stacks, but as far as I know those do not work in this particular use case. Even if they did, I would prefer to avoid using functions not provided with Matlab.

Best Answer

I found something that fixes the issue, but I'm not 100% sure why it works. In any case, I have included the solution below. I will also be reporting this as a bug/documentation problem, as it is definitely not clear in the documentation of the TIFF class.
To fix this, I modified the changePage() function as follows:
function [] = changePage(obj, imageSize, tileSize)
% get tags from last page, before we switch
bitsPerSample = obj.TiffObject.getTag('BitsPerSample');
sFormat = obj.TiffObject.getTag('SampleFormat');
compression = obj.TiffObject.getTag('Compression');
% increments to next directory, so next time we call blockproc
% it will write to the next page
obj.TiffObject.writeDirectory();
% now write out new metadata for the new page
obj.ImageSize = [imageSize(1), imageSize(2), 1]; %length first
obj.TileLength = tileSize(1);
obj.TileWidth = tileSize(2);
obj.TiffObject.setTag('ImageLength', obj.ImageSize(1));
obj.TiffObject.setTag('ImageWidth', obj.ImageSize(2));
obj.TiffObject.setTag('TileLength', obj.TileLength);
obj.TiffObject.setTag('TileWidth', obj.TileWidth);
obj.TiffObject.setTag('Photometric', Tiff.Photometric.MinIsBlack);
obj.TiffObject.setTag('BitsPerSample', bitsPerSample);
obj.TiffObject.setTag('SampleFormat', sFormat);
obj.TiffObject.setTag('SamplesPerPixel', 1);
obj.TiffObject.setTag('PlanarConfiguration', Tiff.PlanarConfiguration.Chunky);
obj.TiffObject.setTag('Compression',compression);
end
Essentially, I added the rest of the tags that came from the first page. As soon as call to setTag() for Photometric is called, the TiffObject magically contains the entries of previous setTag calls. At first I thought it was because Photometric makes another call to the Tiff class within it, so I rearranged the setTag calls like this:
obj.TiffObject.setTag('Photometric', Tiff.Photometric.MinIsBlack);
obj.TiffObject.setTag('BitsPerSample', bitsPerSample);
obj.TiffObject.setTag('SampleFormat', sFormat);
obj.TiffObject.setTag('SamplesPerPixel', 1);
obj.TiffObject.setTag('PlanarConfiguration', Tiff.PlanarConfiguration.Chunky);
obj.TiffObject.setTag('Compression',compression);
obj.TiffObject.setTag('ImageLength', obj.ImageSize(1));
obj.TiffObject.setTag('ImageWidth', obj.ImageSize(2));
obj.TiffObject.setTag('TileLength', obj.TileLength);
obj.TiffObject.setTag('TileWidth', obj.TileWidth);
Here, the TiffObject did not update until the setTag for ImageLength was called. What this tells me is that in order to update the TiffObject, a certain number of bytes must be written to the metadata. This is not at all clear in the documentation. I thought that rewriteDirectory() would work, but it does not update the TiffObject. It may actually write out to file like it says, but without the corresponding update to the TiffObject the code will crash the next time a tile is written, because it thinks the TileLength/TileWidth have not been set.
I am still open to answers on better ways to do my multi-resolution file writing, as this seems a bit haphazard, so feel free to add.