Code covered by the BSD License  

Highlights from
TiffHeader

from TiffHeader by Lad Dombrowski
Function to extract header information from .tif, and Metamorph .stk and multitif image files.

OutputTiffHeader( filename )
%--------------------------------------------------------------------------
function  data = OutputTiffHeader( filename )
% -------------------------------------------------------------------------
%       Name: OutputTiffHeader
%       Disc: function to extract header information
%           
%       Input: filename - image file to extract data from
%
%       Output: data - image header dataj
%
%       Owner: Lad Dombrowski
%                       Center for Live Cell Imaging
%                       Department of Microbiology and Immunology
%                       University of Michigan
%                       http://sitemaker.umich.edu/4dimagingcenter/center_for_live-cell_imaging_home
%               
%       Files needed: TiffHeader.fig, TiffHeader.m, OutputTiffHeader.m
%                       
%               
%------------------------------------------------------------------------
%
% This software is based on the tiffread software 
% provided at no cost by a public research institution.
%
% Francois Nedelec
% nedelec (at) embl.de
% Cell Biology and Biophysics, EMBL; Meyerhofstrasse 1; 69117 Heidelberg; Germany
% http://www.embl.org
% http://www.cytosim.org

data(1).filename = ['filename = ' filename];

%Optimization: join adjacent TIF strips: this results in faster reads
consolidateStrips = 1;

%if there is no argument, we ask the user to choose a file:
if (nargin == 0)
    [filename, pathname] = uigetfile('*.tif;*.stk;*.lsm', 'select image file');
    filename = [ pathname, filename ];
end

% if (nargin<=1)  img_first = 1; img_last = 10000; end
% if (nargin==2)  img_last = img_first;            end

% default to everything
img_first = 1;
img_last = 10000;

% not all valid tiff tags have been included, as they are really a lot...
% if needed, tags can easily be added to this code
% See the official list of tags:
% http://partners.adobe.com/asn/developer/pdfs/tn/TIFF6.pdf
%
% the structure IMG is returned to the user, while TIF is not.
% so tags usefull to the user should be stored as fields in IMG, while
% those used only internally can be stored in TIF.

global TIF;
TIF = [];

%counters for the number of images read and skipped
img_skip  = 0;
img_read  = 0;

% set defaults values :
TIF.SampleFormat     = 1;
TIF.SamplesPerPixel  = 1;
TIF.BOS              = 'l';          %byte order string

if  isempty(findstr(filename,'.'))
    filename = [filename,'.tif'];
end

[TIF.file, message] = fopen(filename,'r','l');
if TIF.file == -1
    filename = strrep(filename, '.tif', '.stk');
    [TIF.file, message] = fopen(filename,'r','l');
    if TIF.file == -1
        error(['file <',filename,'> not found.']);
    end
end

% read header


% keep track of the number of images
imageIndex = 1;

% read byte order: II = little endian, MM = big endian
byte_order = setstr(fread(TIF.file, 2, 'uchar'));

if ( strcmp(byte_order', 'II') )
    TIF.BOS = 'l';                                %normal PC format
    data(imageIndex,1).byteOrder = 'Byte Order = II';
elseif ( strcmp(byte_order','MM') )
    TIF.BOS = 'b';
    data(imageIndex,1).byteOrder = 'Byte Order = MM';
else
    error('This is not a TIFF file (no MM or II).');
end

%----- read in a number which identifies file as TIFF format
tiff_id = fread(TIF.file,1,'uint16', TIF.BOS);
if (tiff_id ~= 42)
    error('This is not a TIFF file (missing 42).');
end

% create dummy exposures for .tifs - ljd (5-22-08)
TIF.info.Exposure = 1;

%----- read the byte offset for the first image file directory (IFD)
ifd_pos = fread(TIF.file,1,'uint32', TIF.BOS);

while (ifd_pos ~= 0)

    clear IMG;
    IMG.filename = fullfile( pwd, filename );

    % move in the file to the first IFD
    fseek(TIF.file, ifd_pos, -1);
    data(imageIndex,1).position = ['reading img at pos : ' num2str(ifd_pos)];

    %read in the number of IFD entries
    num_entries = fread(TIF.file,1,'uint16', TIF.BOS);
    data(imageIndex,1).numEntries = ['num_entries = ' num2str(num_entries)];

    %read and process each IFD entry
    for i = 1:num_entries

        % save the current position in the file
        file_pos  = ftell(TIF.file);
        data(imageIndex,i).filePosition = ['File Position = ' num2str(file_pos)];

        % read entry tag
        TIF.entry_tag = fread(TIF.file, 1, 'uint16', TIF.BOS);

        entry = readIFDentry;
        
        data(imageIndex,i).entryTag = ['Entry tag = ' num2str(TIF.entry_tag)];
 
        %disp(strcat('reading entry <',num2str(TIF.entry_tag),'>'));
        switch TIF.entry_tag
            case 254
                TIF.NewSubfiletype = entry.val;
                data(imageIndex,i).string = ['Sub-file Type = ' num2str(entry.val)];
            case 256         % image width - number of column
                IMG.width          = entry.val;
                data(imageIndex,i).string = ['Image Width = ' num2str(entry.val)];
            case 257         % image height - number of row
                IMG.height         = entry.val;
                TIF.ImageLength    = entry.val;
                data(imageIndex,i).string = ['Image Height = ' num2str(entry.val)];
            case 258         % BitsPerSample per sample
                TIF.BitsPerSample  = entry.val;
                TIF.BytesPerSample = TIF.BitsPerSample / 8;
                IMG.bits           = TIF.BitsPerSample(1);
                data(imageIndex,i).string = ['Bits per sample = ' num2str(IMG.bits)];
                %fprintf(1,'BitsPerSample %i %i %i\n', entry.val);
            case 259         % compression
                if (entry.val ~= 1) error('Compression format not supported.'); end
                data(imageIndex,i).string = ['Compression = ' num2str(entry.val)];
            case 262         % photometric interpretation
                TIF.PhotometricInterpretation = entry.val;
                data(imageIndex,i).string = ['Photometric Interpretation = ' num2str(entry.val)];
                if ( TIF.PhotometricInterpretation == 3 )
                    fprintf(1, 'warning: ignoring the look-up table defined in the TIFF file');
                end
            case 269
                IMG.document_name  = entry.val;
                data(imageIndex,i).string = ['Document Name = ' entry.val];
            case 270         % comment:
                TIF.info           = parseMM_info(entry.val,i);
                data(imageIndex,i).string = 'Metamorph info';
                data(imageIndex,i).mmInfo = TIF.info;
            case 271
                IMG.make = entry.val;
                data(imageIndex,i).string = ['Make = ' num2str(entry.val)];
            case 273         % strip offset
                TIF.StripOffsets   = entry.val;
                TIF.StripNumber    = entry.cnt;
                data(imageIndex,i).string = ['Strip Offsets/Number = ' num2str(entry.val(1)) '/' num2str(entry.cnt)];
                %fprintf(1,'StripNumber = %i, size(StripOffsets) = %i %i\n', TIF.StripNumber, size(TIF.StripOffsets));
            case 277         % sample_per pixel
                TIF.SamplesPerPixel  = entry.val;
                data(imageIndex,i).string = ['Color image: sample per pixel = ' num2str(entry.val)];
                %fprintf(1,'Color image: sample_per_pixel=%i\n',  TIF.SamplesPerPixel);
            case 278         % rows per strip
                TIF.RowsPerStrip   = entry.val;
                data(imageIndex,i).string = ['Rows per strip = ' num2str(entry.val)];
            case 279         % strip byte counts - number of bytes in each strip after any compressio
                TIF.StripByteCounts= entry.val;
                data(imageIndex,i).string = ['Strip Byte Count = ' num2str(entry.val(1))];
            case 282         % X resolution
                IMG.x_resolution   = entry.val;
                data(imageIndex,i).string = ['X resolution = ' num2str(entry.val(1)) ',' num2str(entry.val(2))];
            case 283         % Y resolution
                IMG.y_resolution   = entry.val;
                data(imageIndex,i).string = ['Y resolution = ' num2str(entry.val(1)) ',' num2str(entry.val(2))];
            case 284         %planar configuration describe the order of RGB
                TIF.PlanarConfiguration = entry.val;
                data(imageIndex,i).string = ['Planar configuration = ' num2str(entry.val)];
            case 296         % resolution unit
                IMG.resolution_unit= entry.val;
                data(imageIndex,i).string = ['Resolution units = ' entry.typechar];
            case 305         % software
                IMG.software       = entry.val;
                len = length(entry.val);
                name = '';
                for iname = 1:len
                    name = [name entry.val(iname)];
                end
                data(imageIndex,i).string = ['Software = ' name];
            case 306         % datetime
                IMG.datetime = entry.val;
                len = length(entry.val);
                filedate = '';
                for idate = 1:len
                    filedate = [filedate entry.val(idate)];
                end
                data(imageIndex,i).string = ['Date-Time = ' filedate];
            case 315
                IMG.artist         = entry.val;
                data(imageIndex,i).string = ['Artist = ' entry.val];
            case 317        %predictor for compression
                data(imageIndex,i).string = ['Predictor for compression = ' num2str(entry.val)];
                if (entry.val ~= 1) error('unsuported predictor value'); end
            case 320         % color map
                IMG.cmap           = entry.val;
                IMG.colors         = entry.cnt/3;
                data(imageIndex,i).string = ['Color Map/Colors = ' num2str(entry.val) '/' num2str(entry.cnt/3)];
            case 339
                TIF.SampleFormat   = entry.val;
                data(imageIndex,i).string = ['Sample Format = ' num2str(entry.val)];
                if (( TIF.SampleFormat ~= 1) && (TIF.SampleFormat ~= 3))
                    error(sprintf('unsuported sample format = %i', TIF.SampleFormat));
                end
            case 33628       %metamorph specific data
                IMG.MM_private1    = entry.val;
                % ljd 6-4-08 added illumination setting and creation time variables
                if (isfield(TIF, 'MM_illumSetting'))
                    IMG.MM_illumSetting = TIF.MM_illumSetting;
                end
                if (isfield(TIF, 'MM_ctime'))
                    IMG.MM_ctime = TIF.MM_ctime;
                end
                data(imageIndex,i).string = 'Metamorph Private Data';
            case 33629       %this tag identify the image as a Metamorph stack!
                TIF.MM_stack       = entry.val;
                TIF.MM_stackCnt    = entry.cnt;
                data(imageIndex,i).string = ['Metamorph Stack - count = ' num2str(entry.cnt)];
                if ( img_last > img_first )
                    waitbar_handle = waitbar(0,'Please wait...','Name',['Reading ' filename]);
                end
            case 33630       %metamorph stack data: wavelength  
                TIF.MM_wavelength  = entry.val;
                data(imageIndex,i).string = ['Metamorph wavelength = ' num2str(entry.val(1)) ',' num2str(entry.val(2))];
            case 33631       %metamorph stack data: gain/background?
                TIF.MM_private2    = entry.val;
                data(imageIndex,i).string = ['Metamorph - gain/background = ' num2str(entry.val(1)) ...
                                ',' num2str(entry.val(2)) ];
            case 34412       % Zeiss LSM data (I have no idea what that represents...)
                IMG.LSM            = entry.val;
                data(imageIndex,i).string = ['Zeiss LSM data = ' num2str(entry.val)];
            otherwise
                data(imageIndex,i).string = ['Unrecognized tag = ' num2str(TIF.entry_tag)];
                fprintf(1,'ignored TIFF entry with tag %i (cnt %i)\n', TIF.entry_tag, entry.cnt);
        end
        % move to next IFD entry in the file
        fseek(TIF.file, file_pos+12,-1);
    end

    %Planar configuration is not fully supported
    %Per tiff spec 6.0 PlanarConfiguration irrelevent if SamplesPerPixel==1
    %Contributed by Stephen Lang
    if ((TIF.SamplesPerPixel ~= 1) && (TIF.PlanarConfiguration == 1))
        error(sprintf('PlanarConfiguration = %i not supported', TIF.PlanarConfiguration));
    end

    %total number of bytes per image:
    PlaneBytesCnt = IMG.width * IMG.height * TIF.BytesPerSample;

    data(imageIndex,i).bytesPerImage = ['Bytes per image = ' num2str(PlaneBytesCnt)];
    
    if consolidateStrips
        %Try to consolidate the strips into a single one to speed-up reading:
        BytesCnt = TIF.StripByteCounts(1);

        if BytesCnt < PlaneBytesCnt

            ConsolidateCnt = 1;
            %Count how many Strip are needed to produce a plane
            while TIF.StripOffsets(1) + BytesCnt == TIF.StripOffsets(ConsolidateCnt+1)
                ConsolidateCnt = ConsolidateCnt + 1;
                BytesCnt = BytesCnt + TIF.StripByteCounts(ConsolidateCnt);
                if ( BytesCnt >= PlaneBytesCnt ) break; end
            end

            %Consolidate the Strips
            if ( BytesCnt <= PlaneBytesCnt(1) ) && ( ConsolidateCnt > 1 )
                %fprintf(1,'Consolidating %i stripes out of %i', ConsolidateCnt, TIF.StripNumber);
                TIF.StripByteCounts = [BytesCnt; TIF.StripByteCounts(ConsolidateCnt+1:TIF.StripNumber ) ];
                TIF.StripOffsets = TIF.StripOffsets( [1 , ConsolidateCnt+1:TIF.StripNumber] );
                TIF.StripNumber  = 1 + TIF.StripNumber - ConsolidateCnt;
            end
        end
    end

    %read the next IFD address:
    ifd_pos = fread(TIF.file, 1, 'uint32', TIF.BOS);
    %if (ifd_pos) disp(['next ifd at', num2str(ifd_pos)]); end
    
    if (ifd_pos)
        data(imageIndex,i).ifd = ['Next Ifd at = ' num2str(ifd_pos)];
    end

    if isfield( TIF, 'MM_stack' )
        
        if ( img_last > TIF.MM_stackCnt )
            img_last = TIF.MM_stackCnt;
        end

        %this loop is to read metamorph stacks:
        for ii = img_first:img_last

            TIF.StripCnt = 1;

            %read the image
            fileOffset = PlaneBytesCnt * ( ii - 1 );
            data(imageIndex,i).mmImage(ii).planeNo = ['Reading Metamorph image # ' num2str(ii)];
            data(imageIndex,i).mmImage(ii).fieldOffset = ['Field offset = ' num2str(fileOffset)];
            %fileOffset = 0;
            %fileOffset = ftell(TIF.file) - TIF.StripOffsets(1);

            if ( TIF.SamplesPerPixel == 1 )
                IMG.data  = read_plane(fileOffset, IMG.width, IMG.height, 1);
            else
                IMG.red   = read_plane(fileOffset, IMG.width, IMG.height, 1);
                IMG.green = read_plane(fileOffset, IMG.width, IMG.height, 2);
                IMG.blue  = read_plane(fileOffset, IMG.width, IMG.height, 3);
            end

            % print a text timer on the main window, or update the waitbar
            % fprintf(1,'img_read %i img_skip %i\n', img_read, img_skip);
            if exist('waitbar_handle', 'var') && (round(ii/10) == ii/10)
                waitbar( img_read/TIF.MM_stackCnt, waitbar_handle);
            end
            
            [ IMG.info, IMG.MM_stack, IMG.MM_wavelength, IMG.MM_private2 ] = extractMetamorphData(ii);
            
            data(imageIndex,i).mmImage(ii).wavelength = ['Metamorph wavelength = ' num2str(IMG.MM_wavelength(1)), ',', ...
                                    num2str(IMG.MM_wavelength(2))];
            
            img_read = img_read + 1;
            stack( img_read ) = IMG;

        end
        break;

    else

        %this part to read a normal TIFF stack:

        if ( img_skip + 1 >= img_first )

            TIF.StripCnt = 1;
            %read the image
            if ( TIF.SamplesPerPixel == 1 )
                IMG.data  = read_plane(0, IMG.width, IMG.height, 1);
            else
                IMG.red   = read_plane(0, IMG.width, IMG.height, 1);
                IMG.green = read_plane(0, IMG.width, IMG.height, 2);
                IMG.blue  = read_plane(0, IMG.width, IMG.height, 3);
            end

            img_read = img_read + 1;

            try
                % ljd - added Exposure to .tif images data (5-22-08)
                IMG.info = TIF.info;
                stack( img_read ) = IMG;
            catch
                %stack
                %IMG
                error('The file contains dissimilar images: you can only read them one by one');
            end
        else
            img_skip = img_skip + 1;
        end

        if ( img_skip + img_read >= img_last )
            break;
        end
    end
    
    % increment image counter
    imageIndex = imageIndex + 1;
end

%clean-up
fclose(TIF.file);
if exist('waitbar_handle', 'var')
    delete( waitbar_handle );
    clear waitbar_handle;
end
drawnow;
%return empty array if nothing was read
if ~ exist( 'stack', 'var')
    stack = [];
end

return;


%============================================================================

function plane = read_plane(offset, width, height, planeCnt )

global TIF;

%return an empty array if the sample format has zero bits
if ( TIF.BitsPerSample(planeCnt) == 0 )
    plane=[];
    return;
end

%fprintf(1,'reading plane %i size %i %i\n', planeCnt, width, height);

% Preallocate the matrix to hold the data:
%string description of the type of integer needed: int8 or int16...
typecode = sprintf('int%i', TIF.BitsPerSample(planeCnt) );
%unsigned int if SampleFormat == 1
if ( TIF.SampleFormat == 1 )
    typecode = [ 'u', typecode ];
    % Preallocate a matrix to hold the sample data:
    plane = eval( [ typecode, '(zeros(width, height))'] );
elseif ( TIF.SampleFormat == 3 )
    typecode = 'float';
    plane = zeros(width, height);
end

line = 1;

while ( TIF.StripCnt <= TIF.StripNumber )

    strip = read_strip(offset, width, planeCnt, TIF.StripCnt, typecode );
    TIF.StripCnt = TIF.StripCnt + 1;

    % copy the strip onto the data
    plane(:, line:(line+size(strip,2)-1)) = strip;

    line = line + size(strip,2);
    if ( line > height )
        break;
    end

end

% Extract valid part of data if needed
if ~all(size(plane) == [width height]),
    plane = plane(1:width, 1:height);
    error('Cropping data: more bytes read than needed...');
end

% transpose the image
plane = plane';

return;


%=================== sub-functions to read a strip ===================

function strip = read_strip(offset, width, planeCnt, stripCnt, typecode)

global TIF;

%fprintf(1,'reading strip at position %i\n',TIF.StripOffsets(stripCnt) + offset);
StripLength = TIF.StripByteCounts(stripCnt) ./ TIF.BytesPerSample(planeCnt);

%fprintf(1, 'reading strip %i\n', stripCnt);
fseek(TIF.file, TIF.StripOffsets(stripCnt) + offset, 'bof');
bytes = fread( TIF.file, StripLength, typecode, TIF.BOS );

if ( length(bytes) ~= StripLength )
    error('End of file reached unexpectedly.');
end

strip = reshape(bytes, width, StripLength / width);

return;


%===================sub-functions that reads an IFD entry:===================


function [nbbytes, typechar] = matlab_type(tiff_typecode)
switch (tiff_typecode)
    case 1
        nbbytes=1;
        typechar='uint8';
    case 2
        nbbytes=1;
        typechar='uchar';
    case 3
        nbbytes=2;
        typechar='uint16';
    case 4
        nbbytes=4;
        typechar='uint32';
    case 5
        nbbytes=8;
        typechar='uint32';
    otherwise
        error('tiff type not supported')
end
return;

%===================sub-functions that reads an IFD entry:===================

function  entry = readIFDentry()

global TIF;
entry.typecode = fread(TIF.file, 1, 'uint16', TIF.BOS);
entry.cnt      = fread(TIF.file, 1, 'uint32', TIF.BOS);

%disp(['typecode =', num2str(entry.typecode),', cnt = ',num2str(entry.cnt)]);

[ entry.nbbytes, entry.typechar ] = matlab_type(entry.typecode);

if entry.nbbytes * entry.cnt > 4
    %next field contains an offset:
    offset = fread(TIF.file, 1, 'uint32', TIF.BOS);
    %disp(strcat('offset = ', num2str(offset)));
    fseek(TIF.file, offset, -1);
end
if TIF.entry_tag == 33629   %special metamorph 'rationals'
    entry.val = fread(TIF.file, 6*entry.cnt, entry.typechar, TIF.BOS);
elseif TIF.entry_tag == 33628  % MM_private1 - ljd 6-4-08 added code
    %disp(strcat('offset = ', num2str(offset)));
    %disp(['typecode =', num2str(entry.typecode),', cnt = ',num2str(entry.cnt)]);
    entry.val = fread(TIF.file, entry.cnt, entry.typechar, TIF.BOS);
    for i = 1:entry.cnt-1

        switch entry.val(i)
            case 49     % illumsetting, magsetting, magni, magri
                offset = entry.val(i+1);

                if (offset < 300)
                    % nothing
                else
                    fseek(TIF.file, offset, -1);
                    id = fread(TIF.file, 1, 'uint16', TIF.BOS);
                    id = fread(TIF.file, 1, 'uint16', TIF.BOS);
                    id = fread(TIF.file, 1, 'uint16', TIF.BOS);
                    switch id
                        case 24334 % illumSetting
                            %disp('49 = ')
                            name = setstr(fread(TIF.file, 13, 'uint8', TIF.BOS));
                            TIF.MM_illumSetting = setstr(fread(TIF.file, 13, 'uint8', TIF.BOS));
                        case 24332      % MagSetting
                            name = setstr(fread(TIF.file, 23, 'uint8', TIF.BOS));
                        case 24327      % MagRI, MagNA
                            name = setstr(fread(TIF.file, 25, 'uint8', TIF.BOS));
                    end

                    % name = setstr(fread(TIF.file, 1, 'uint32', TIF.BOS));
                    %name
                end
            case 16         % creation time - since midnight in milsec
                %disp('16 = ');
                offset = entry.val(i+1);
                fseek(TIF.file, offset, -1);
                date = fread(TIF.file, 1, 'uint32', TIF.BOS);

                time = fread(TIF.file, 1, 'uint32', TIF.BOS);
                hour = 3600000;
                hours = floor(time/hour);
                time = time - hours*hour;
                minutes = floor(time/60000);
                time = time - minutes*60000;
                sec = floor(time/1000);
                milsec = time - sec*1000;
                TIF.MM_ctime = strcat(num2str(hours), ':', num2str(minutes), ':',...
                    num2str(sec), ':',num2str(milsec));
        end
    end
else
    if entry.typecode == 5
        entry.val = fread(TIF.file, 2*entry.cnt, entry.typechar, TIF.BOS);
    else
        entry.val = fread(TIF.file, entry.cnt, entry.typechar, TIF.BOS);
    end 
end
if ( entry.typecode == 2 )
    entry.val = char(entry.val);
end

return;


%==============distribute the metamorph infos to each frame:
function [info, stack, wavelength, private2 ] = extractMetamorphData(imgCnt)

global TIF;

info = [];
stack = [];
wavelength = [];
private2 = [];

if TIF.MM_stackCnt == 1
    return;
end

left  = imgCnt - 1;

if isfield( TIF, 'info' )
    %         S = length(TIF.info) / TIF.MM_stackCnt;
    infoSize = size(TIF.info);
    if (infoSize(2) == 1)
        info = TIF.info;
    else
        info = TIF.info(imgCnt);
    end
end

if isfield( TIF, 'MM_stack' )
    S = length(TIF.MM_stack) / TIF.MM_stackCnt;
    stack = TIF.MM_stack( [S*left+1:S*left+S] );
end

if isfield( TIF, 'MM_wavelength' )
    S = length(TIF.MM_wavelength) / TIF.MM_stackCnt;
    wavelength = TIF.MM_wavelength( [S*left+1:S*left+S] );
end

if isfield( TIF, 'MM_private2' )
    S = length(TIF.MM_private2) / TIF.MM_stackCnt;
    private2 = TIF.MM_private2( [S*left+1:S*left+S] );
end


return;


%%%%  Parse the Metamorph camera info tag into respective fields
% EVBR 2/7/2005
function mm_infor = parseMM_info(info,p)

i=0;
propCount = 1;
cpropCount = 1;
kk=info;
kk(find(double(kk)==0)) = '';
while(length(kk))
    i=i+1;
    [tmp, kk] = strtok(kk, 13);
    %     [tmp,b] = strtok(tmp, 0);
    token{i} = sscanf(tmp, '%s');
end
tok = {token{1:end-1}};

len = length(tok)/11;
k=1;            % changed default to 1 - for data without exposure
if (length(tok) == 0)
    % more metamorph info data - at least set exposure
    mm_infor(1).Exposure = 1;
end

for i=1:length(tok)
    if ~isempty(token{i})
        [tkk, remk] = strtok(token{i}, 58);

        % if remark is empty - say so LJD - (7-15-08)
        if (isempty(remk))
            remk = ' empty';
        end

        % removed kludge for exposure only - ljd 7-22-08
        %if strcmp(tkk, 'Exposure')
        %    k=k+1;
        %end

        switch tkk
            case 'Exposure'
                % Exposure: 200 ms -> 200
                mm_infor(k).Exposure = str2double(remk(2:strfind(remk, 'ms')-1));
                
                data(p).mmData(i).exposure = ['Metamorph Exposure = ' num2str(mm_infor(k).Exposure)];
            case 'Binning'
                % Binning: 1 x 1 -> [1 1]
                tmp = sscanf(remk, '%c %d %c %d')';
                mm_infor(k).Binning = tmp([2,4]);
                data(p).mmData(i).binning = ['Metamorph Binning = ' mm_infor(k).Binning];
            case 'Region'
                % Region: 1392 x 1040, offset at (0, 0) -> [1392 1040], [0 0]
                [a,b] = strtok(remk, ',');
                tmp = sscanf(a, '%c %d %c %d')';
                mm_infor(k).Region.Size = tmp([2,4]);
                [a,b] = strtok(b, ',');
                mm_infor(k).Region.Offset = [str2double(a(end)), str2double(b(2))];
                data(p).mmData(i).region = ['Metamorph Region = ' a(end), ',' b(2)];
            case 'Subtract'
                % Subtract: Off -> 0
                mm_infor(k).Subtract = ~strcmp(remk(2:end),'Off');
                data(p).mmData(i).subtract = ['Metamorph Subtract = ',  remk(2:end)];
            case 'Shading'
                % Shading: Off -> 0
                mm_infor(k).Shading = ~strcmp(remk(2:end),'Off');
                data(p).mmData(i).shading = ['Metamorph Shading = ',  remk(2:end)];
            case 'Digitizer'
                % Digitizer: 5MHz -> 5
                mm_infor(k).Digitizer = sscanf(remk(2:end), '%d %*s');
                data(p).mmData(i).digitize = ['Metamorph Digitizer = ',  sscanf(remk(2:end), '%d %*s')'];
            case 'Gain'
                % Gain: Gain 1 (1x) -> 1
                mm_infor(k).Gain = sscanf(remk(strfind(remk,'(')+1:end),...
                    '%d %*s %d')';
                data(p).mmData(i).gain = ['Metamorph Gain = ',  sscanf(remk(strfind(remk,'(')+1:end),...
                    '%d %*s %d')'];
            case 'CameraShutter'
                % Camera Shutter: Always Open -> 'AlwaysOpen'
                mm_infor(k).CameraShutter = remk(2:end);
                data(p).mmData(i).cameraShutter = ['Metamorph Camera Shutter = ',  remk(2:end)];
            case 'ClearCount'
                % Clear Count: 2 -> 2
                tmp = sscanf(remk, '%c %d')';
                mm_infor(k).ClearCount = tmp(2);
                data(p).mmData(i).clearCount = ['Metamorph Clear Count = ',  num2str(tmp(2))];
            case 'TriggerMode'
                % Trigger Mode: Normal -> 'Normal'
                mm_infor(k).TriggerMode = remk(2:end);
                data(p).mmData(i).tiggerMode = ['Metamorph Trigger mode = ',  remk(2:end)];
            case 'Temperature'
                % Temperature: 20 -> 20
                tmp = sscanf(remk, '%c %d')';
                mm_infor(k).Temperature = tmp(2);
                data(p).mmData(i).temp = ['Metamorph Camera Shutter = ',  num2str(tmp(2))];
            case 'ClearMode'        % ljd - added ClearMode case (5-30-08)
                % Clear Mode: Normal -> 'Normal'
                mm_infor(k).ClearMode = remk(2:end);
                data(p).mmData(i).clearMode = ['Metamorph Clear Mode = ',  remk(2:end)];
            case 'FramestoAverage'  % ljd - FramestoAverage (5-30-08)
                % FramestoAverage: Normal -> 'Normal'
                mm_infor(k).FramestoAverage = remk(2:end);
                data(p).mmData(i).frames = ['Metamorph Frame to Average = ',  remk(2:end)];
            case 'AcquiredfromPhotometrics'  % ljd - AcquiredfromPhotometrics (5-30-08)
                % AcquiredfromPhotometrics: Normal -> 'Normal'
                mm_infor(k).AcquiredfromPhotometrics = remk(2:end);
                data(p).mmData(i).photo = ['Metamorph Photometrics = ',  remk(2:end)];
            case 'SensorMode'  % ljd - SensorMode (5-30-08)
                % SensorMode: Normal -> 'Normal'
                mm_infor(k).SensorMode = remk(2:end);
                data(p).mmData(i).sensorMode = ['Metamorph Sensor Mode = ',  remk(2:end)];
            case 'MultiplicationGain'  % ljd - MultiplicationGain (5-30-08)
                % MultiplicationGain: Normal -> 'Normal'
                mm_infor(k).MultiplicationGain = remk(2:end);
                data(p).mmData(i).mult = ['Metamorph Multiplication Gain = ',  remk(2:end)];
            case 'Illumination'  % ljd - Illumination (5-30-08)
                % Illumination: Normal -> 'Normal'
                mm_infor(k).Illumination = remk(2:end);
                data(p).mmData(i).illumination = ['Metamorph Illumination = ',  remk(2:end)];
            case '[IntensityMapping]'  % ljd - IntensityMapping (7-23-09)
                % IntensityMapping: Normal -> 'MapCh0'
                p = 1;
                index = i+1;
                [tkk, remk] = strtok(token{index}, 58);
                while (strcmpi(tkk,'[IntensityMappingEnd]') == 0)
                    mm_infor(k).IntensityMapping{p} = tkk;
                    index = index+1;
                    [tkk, remk] = strtok(token{index}, 58);
                    p = p+1;
                end
                data(p).mmData(i).intensity = mm_infor(k).IntensityMapping;
            case '[DisplaySettings]'  % ljd - DisplaySettings (7-23-09)
                % DisplaySettings: Normal -> 'Gamma0=1,DisplayMode=545,DisplayZoom=1'
                 p = 1;
                index = i+1;
                [tkk, remk] = strtok(token{index}, 58);
                while (strcmpi(tkk,'[DisplaySettingsEnd]') == 0)
                    mm_infor(k).displaySettings{p} = tkk;
                    index = index+1;
                    [tkk, remk] = strtok(token{index}, 58);
                    p = p+1;
                end
                data(p).mmData(i).display = mm_infor(k).displaySettings;
            case '[AcquisitionParameters]'  % ljd - AquisitionParameters (7-23-09)
                % MultiplicationGain: Normal -> 'Normal'
                 p = 1;
                index = i+1;
                [tkk, remk] = strtok(token{index}, 58);
                while (strcmpi(tkk,'[AcquisitionParametersEnd]') == 0)
                    mm_infor(k).acquisition{p} = tkk;
                    index = index+1;
                    [tkk, remk] = strtok(token{index}, 58);
                    p = p+1;
                end
                data(p).mmData(i).acquisition = mm_infor(k).acquisition;
            case '[Description]'  % ljd - Description (7-23-09)
                % Description: Normal -> 'Normal'
                 p = 1;
                index = i+1;
                [tkk, remk] = strtok(token{index}, 58);
                while (strcmpi(tkk,'[DescriptionEnd]') == 0)
                    mm_infor(k).description{p} = tkk;
                    index = index+1;
                    [tkk, remk] = strtok(token{index}, 58);
                    p = p+1;
                end
                data(p).mmData(i).description = mm_infor(k).description;
            case '[Physiology]'  % ljd - Physiology (7-23-09)
                % Physiology: Normal -> 'Normal'
                 p = 1;
                index = i+1;
                [tkk, remk] = strtok(token{index}, 58);
                while (strcmpi(tkk,'[PhysiologyEnd]') == 0)
                    mm_infor(k).physiology{p} = tkk;
                    index = index+1;
                    [tkk, remk] = strtok(token{index}, 58);
                    p = p+1;
                end
                data(p).mmData(i).physiology = mm_infor(k).physiology;
            case '[LUTCh0]'  % ljd - LUTCh0 (7-23-09)
                % LUTCh0: Normal -> 'Normal'
                 p = 1;
                index = i+1;
                [tkk, remk] = strtok(token{index}, 58);
                while (strcmpi(tkk,'[LUTCh0End]') == 0)
                    mm_infor(k).LUTCh0{p} = tkk;
                    index = index+1;
                    [tkk, remk] = strtok(token{index}, 58);
                    p = p+1;
                end
                data(p).mmData(i).LUTCh0 = mm_infor(k).LUTCh0;
            case '[VersionInfo]'  % ljd - VersionInfo (7-23-09)
                % VersionInfo: Normal -> 'Normal'
                 p = 1;
                index = i+1;
                [tkk, remk] = strtok(token{index}, 58);
                while (strcmpi(tkk,'[VersionInfoEnd]') == 0)
                    mm_infor(k).versionInfo{p} = tkk;
                    index = index+1;
                    [tkk, remk] = strtok(token{index}, 58);
                    p = p+1;
                end
                data(p).mmData(i).version = mm_infor(k).versionInfo;
            case '<MetaData>'  % ljd - multitif metadata (8-28-09)
                index = i+1;
                [tkk, remk] = strtok(token{index}, 13);
                
                % extract the metaData fields
                tmpInfo = decodeTkk(tkk);
                
                if (isfield(tmpInfo,'Exposure'))
                    mm_infor(k).Exposure = tmpInfo.Exposure;
                    data(p).mmData(i).exposure = ['Metamorph Exposure = ' ...
                        num2str(mm_infor(k).Exposure)];
                end
                if (isfield(tmpInfo,'Binning'))
                   mm_infor(k).Binning = tmpInfo.Binning;
                   data(p).mmData(i).binning = ['Metamorph Binning = ' ...
                       '[' num2str(mm_infor(k).Binning(1)) ' x ' ...
                       num2str(mm_infor(k).Binning(2)) ']'];
                end
                if (isfield(tmpInfo,'Region'))
                   mm_infor(k).Region = tmpInfo.Region;
                   sizeString = [' Size(' num2str(tmpInfo.Region.Size(1)) ',' ...
                       num2str(tmpInfo.Region.Size(2)) ') '];
                   offString = [' Offset at(' num2str(tmpInfo.Region.Offset(1)) ...
                       ',' num2str(tmpInfo.Region.Offset(2)) ') '];
                   data(p).mmData(i).region = ['Metamorph Region = '...
                       sizeString offString];
                end
                if (isfield(tmpInfo,'Subtract'))
                   mm_infor(k).Subtract = tmpInfo.Subtract;
                   if (tmpInfo.Subtract)
                       data(p).mmData(i).subtract = ['Metamorph Subtract = On'];
                   else
                       data(p).mmData(i).subtract = ['Metamorph Subtract = Off'];
                   end
                end
                if (isfield(tmpInfo,'Shading'))
                   mm_infor(k).Shading = tmpInfo.Shading;
                  if (tmpInfo.Shading)
                       data(p).mmData(i).shading = ['Metamorph Shading = On'];
                   else
                       data(p).mmData(i).shading = ['Metamorph Shading = Off'];
                  end
                end
                if (isfield(tmpInfo,'Digitizer'))
                    mm_infor(k).Digitizer = tmpInfo.Digitizer;
                    data(p).mmData(i).digitize = ['Metamorph Digitizer = ' ...
                        mm_infor(k).Digitizer];
                end
                if (isfield(tmpInfo,'Gain'))
                    mm_infor(k).Gain = tmpInfo.Gain;
                    data(p).mmData(i).gain = ['Metamorph Gain = ' ...
                        num2str(tmpInfo.Gain(1)) '('  num2str(tmpInfo.Gain(2)) 'x)'];
                end
                if (isfield(tmpInfo,'CameraShutter'))
                   mm_infor(k).CameraShutter = tmpInfo.CameraShutter;
                   data(p).mmData(i).temp = ['Metamorph Camera Shutter = '  ...
                       mm_infor(k).CameraShutter];
                end
                if (isfield(tmpInfo,'ClearCount'))
                   mm_infor(k).ClearCount = tmpInfo.ClearCount;
                   data(p).mmData(i).clearCount = ['Metamorph Clear Count = '  ...
                       num2str(mm_infor(k).ClearCount)];
                end
                if (isfield(tmpInfo,'ClearMode'))
                    mm_infor(k).ClearMode = tmpInfo.ClearMode;
                    data(p).mmData(i).clearMode = ['Metamorph Clear Mode = '  ...
                        mm_infor(k).ClearMode];
                end
                if (isfield(tmpInfo,'FramestoAverage'))
                    mm_infor(k).FramestoAverage = tmpInfo.FramestoAverage;
                    data(p).mmData(i).frames = ['Metamorph Frame to Average = '  ...
                        num2str(mm_infor(k).FramestoAverage)];
                end
                if (isfield(tmpInfo,'TriggerMode'))
                    mm_infor(k).TriggerMode = tmpInfo.TriggerMode;
                    data(p).mmData(i).tiggerMode = ['Metamorph Trigger mode = ' ...
                        mm_infor(k).TriggerMode];
                end
                if (isfield(tmpInfo,'Temperature'))
                    mm_infor(k).Temperature = tmpInfo.Temperature;
                    data(p).mmData(i).temperature = ['Metamorph Temperature mode = '...
                        num2str(mm_infor(k).Temperature)];
                end
                % &&& to see what's in mm data - uncomment these two lines
                %data(p).mmData(i)
                %mm_infor(k)
            case '<PlaneInfo>'  % ljd - multitif metadata - plane info (8-28-09)  
                index = i+1;
                [tkk, remk] = strtok(token{index}, 13);
                
                equPos = findstr('=',tkk);

                subTkk = tkk(equPos(3)+2:end-2);

                mm_infor(k).planeInfo = subTkk;
                   
                data(p).mmData(i).planeInfo = mm_infor(k).planeInfo;
            case '</prop>'  % ljd - multitif metadata (9-9-09) 
                % ignore 
            case '</custom-prop>'  % ljd - multitif metadata (9-9-09) 
                % ignore 
            case '</PlaneInfo>'  % ljd - multitif metadata (9-9-09) 
                % ignore 
                 
            otherwise
                % check for <prop and <custom_prop
                if (strncmp(tkk,'<prop',5))
                    % found <prop - extract data
                    [id, type, value] = decompose(tkk(6:end-1));
                    mm_infor(k).propid(propCount).id = id;
                    mm_infor(k).propid(propCount).type = type;
                    mm_infor(k).propid(propCount).value = value;
                    data(p).mmData(i).propid(propCount).id = mm_infor(k).propid(propCount).id;
                    data(p).mmData(i).propid(propCount).type = mm_infor(k).propid(propCount).type;
                    data(p).mmData(i).propid(propCount).value = mm_infor(k).propid(propCount).value;
                    propCount = propCount + 1;
                elseif (strncmp(tkk,'<custom-prop',12))
                    % found <custom-prop - extract data
                    [id, type, value] = decompose(tkk(13:end-1));
                    mm_infor(k).cpropid(cpropCount).id = id;
                    mm_infor(k).cpropid(cpropCount).type = type;
                    mm_infor(k).cpropid(cpropCount).value = value;
                    data(p).mmData(i).cpropid(cpropCount).id = mm_infor(k).cpropid(cpropCount).id;
                    data(p).mmData(i).cpropid(cpropCount).type = mm_infor(k).cpropid(cpropCount).type;
                    data(p).mmData(i).cpropid(cpropCount).value = mm_infor(k).cpropid(cpropCount).value;
                    cpropCount = cpropCount + 1;
                else 
                    % unknown html tag  
                    data(p).mmData(i).unknownStrings = tkk;
                end
                
        end
    end
end



function info = decodeTkk(tkk)

% function to decode metatdata string
% go to 'Value" section
equPos = findstr('=',tkk);

% extract everything after 'Value'
subTkk = tkk(equPos(3)+2:end-2);

% find number of entries
ampPos = findstr('&amp;#13;&amp;#10;',subTkk);

dims = size(ampPos);

loc = 1;
for i = 1:dims(2)
    % pull out metadata entries
    mmString = subTkk(loc:ampPos(i)-1);
   % output{i} = mmString;
    mmProp = mmString(1:strfind(mmString,':')-1);
    switch mmProp
        case 'Exposure'
            info.Exposure = str2double(mmString(10:end-2));
        case 'Binning'
            tempk = mmString(9:end);
            tmp = sscanf(tempk, '%d %c %d')';
            info.Binning = tmp([1,3]);
        case 'Region'
            % Region: 1392 x 1040, offset at (0, 0) -> [1392 1040], [0 0]
            [a,b] = strtok(mmString(8:end), ',');
            tmp = sscanf(a, '%d %c %d')';
            info.Region.Size = tmp([1,3]);
            [a,b] = strtok(b, ',');
            info.Region.Offset = [str2double(a(end)), str2double(b(2))];
        case 'Subtract'
            % Subtract: Off -> 0
            info.Subtract = ~strcmp(mmString(10:end),'Off');
        case 'Digitizer'
            % Digitizer: 5MHz -> 5
            info.Digitizer = sscanf(mmString(11:end), '%d %*s');
        case 'Shading'
            % Shading: Off -> 0
            info.Shading = ~strcmp(mmString(9:end),'Off');
        case 'Gain'
            % Gain: Gain 1 (1x) -> 1j
            tempk = mmString(strfind(mmString,'(')-1:end);
            tmp = sscanf(tempk, '%d %c %d')';
            info.Gain = tmp([1,3]);
        case 'CameraShutter'
            % Camera Shutter: Always Open -> 'AlwaysOpen'
            info.CameraShutter = mmString(15:end);
        case 'ClearCount'
            % Clear Count: 2 -> 2
            info.ClearCount = str2num(mmString(12:end));
        case 'ClearMode'
            % Clear Mode: Normal -> 'Normal'
            info.ClearMode = mmString(11:end);
        case 'FramestoAverage'
            % FramestoAverage: Normal -> 'Normal'
            info.FramestoAverage = str2num(mmString(17:end));
        case 'TriggerMode'
            % Trigger Mode: Normal -> 'Normal'
            info.TriggerMode = mmString(13:end);
        case 'Temperature'
            % Temperature: 20 -> 20
            info.Temperature = str2num(mmString(13:end));    
        otherwise
            mmProp;
    end
    loc = ampPos(i)+18;
end


function [id, type, value] = decompose(propString)

% propString has format id="xxx"type="xxxx"value="xxxx"

typePos = 1;
typePos = findstr('type=',propString);

valuePos = 1;
valuePos = findstr('value=',propString);

id = propString(5:typePos-2);

type = propString(typePos+6:valuePos-2);

value = propString(valuePos+7:end-1);


Contact us at files@mathworks.com