function [result] = bfopen(id)
% A script for opening microscopy images in MATLAB using Bio-Formats.
%
% The function returns a list of image series; i.e., a cell array of cell
% arrays of (matrix, label) pairs, with each matrix representing a single
% image plane, and each inner list of matrices representing an image
% series. See below for examples of usage.
%
% Portions of this code were adapted from:
% http://www.mathworks.com/support/solutions/en/data/1-2WPAYR/
%
% This method is ~1.5x-2.5x slower than Bio-Formats's command line
% showinf tool (MATLAB 7.0.4.365 R14 SP2 vs. java 1.6.0_20),
% due to overhead from copying arrays.
%
% Thanks to all who offered suggestions and improvements:
%     * Ville Rantanen
%     * Brett Shoelson
%     * Martin Offterdinger
%     * Tony Collins
%     * Cris Luengo
%     * Arnon Lieber
%     * Jimmy Fong
%
% NB: Internet Explorer sometimes erroneously renames the Bio-Formats library
%     to loci_tools.zip. If this happens, rename it back to loci_tools.jar.
%
% Here are some examples of accessing data using the bfopen function:
%
%     % read the data using Bio-Formats
%     data = bfopen('C:/data/experiment.lif');
%
%     % unwrap some specific image planes from the result
%     numSeries = size(data, 1);
%     series1 = data{1, 1};
%     series2 = data{2, 1};
%     series3 = data{3, 1};
%     metadataList = data{1, 2};
%     % ...etc.
%     series1_numPlanes = size(series1, 1);
%     series1_plane1 = series1{1, 1};
%     series1_label1 = series1{1, 2};
%     series1_plane2 = series1{2, 1};
%     series1_label2 = series1{2, 2};
%     series1_plane3 = series1{3, 1};
%     series1_label3 = series1{3, 2};
%     % ...etc.
%
%     % plot the 1st series's 1st image plane in a new figure
%     series1_colorMaps = data{1, 3};
%     figure('Name', series1_label1);
%     if isempty(series1_colorMaps{1})
%         colormap(gray);
%     else
%         colormap(series1_colorMaps{1});
%     end
%     imagesc(series1_plane1);
%
%     % Or if you have the image processing toolbox, you could use:
%     % imshow(series1_plane1, []);
%
%     % Or animate as a movie (assumes 8-bit unsigned data)
%     v = linspace(0, 1, 256)';
%     cmap = [v v v];
%     for p = 1:series1_numPlanes
%         M(p) = im2frame(uint8(series1{p, 1}), cmap);
%     end
%     movie(M);
%
%     % Query some metadata fields (keys are format-dependent)
%     subject = metadataList.get('Subject');
%     title = metadataList.get('Title');

% -- Configuration - customize this section to your liking --

% Toggle the autoloadBioFormats flag to control automatic loading
% of the Bio-Formats library using the javaaddpath command.
%
% For static loading, you can add the library to MATLAB's class path:
%     1. Type "edit classpath.txt" at the MATLAB prompt.
%     2. Go to the end of the file, and add the path to your JAR file
%        (e.g., C:/Program Files/MATLAB/work/loci_tools.jar).
%     3. Save the file and restart MATLAB.
%
% There are advantages to using the static approach over javaaddpath:
%     1. If you use bfopen within a loop, it saves on overhead
%        to avoid calling the javaaddpath command repeatedly.
%     2. Calling 'javaaddpath' may erase certain global parameters.
autoloadBioFormats = 1;

% Toggle the stitchFiles flag to control grouping of similarly
% named files into a single dataset based on file numbering.
stitchFiles = 0;

% To work with compressed Evotec Flex, fill in your LuraWave license code.
%lurawaveLicense = 'xxxxxx-xxxxxxx';

% -- Main function - no need to edit anything past this point --

% load the Bio-Formats library into the MATLAB environment
if autoloadBioFormats
    path = fullfile(fileparts(mfilename('fullpath')), '../plugins/loci_tools.jar');
    javaaddpath(path);
end

% set LuraWave license code, if available
if exist('../jars/lurawaveLicense')
    path = fullfile(fileparts(mfilename('fullpath')), '../jars/lwf_jsdk2.6.jar');
    javaaddpath(path);
    java.lang.System.setProperty('lurawave.license', fullfile(fileparts(mfilename('fullpath')), '../jars/lurawaveLicense'));
end

% check MATLAB version, since typecast function requires MATLAB 7.1+
canTypecast = versionCheck(version, 7, 1);

% check Bio-Formats version, since makeDataArray2D function requires trunk
bioFormatsVersion = char(loci.formats.FormatTools.VERSION);
isBioFormatsTrunk = versionCheck(bioFormatsVersion, 5, 0);

r = loci.formats.ChannelFiller();
r = loci.formats.ChannelSeparator(r);
if stitchFiles
    r = loci.formats.FileStitcher(r);
end

tic
r.setId(id);
numSeries = r.getSeriesCount();
result = cell(numSeries, 2);
for s = 1:numSeries
    fprintf('Reading series #%d', s);
    r.setSeries(s - 1);
    width = r.getSizeX();
    height = r.getSizeY();
    pixelType = r.getPixelType();
    bpp = loci.formats.FormatTools.getBytesPerPixel(pixelType);
    fp = loci.formats.FormatTools.isFloatingPoint(pixelType);
    sgn = loci.formats.FormatTools.isSigned(pixelType);
    bppMax = power(2, bpp * 8);
    little = r.isLittleEndian();
    numImages = r.getImageCount();
    imageList = cell(numImages, 2);
    colorMaps = cell(numImages);
    for i = 1:numImages
        if mod(i, 72) == 1
            fprintf('\n    ');
        end
        fprintf('.');
        plane = r.openBytes(i - 1);

        % retrieve color map data
        if bpp == 1
            colorMaps{s, i} = r.get8BitLookupTable()';
        else
            colorMaps{s, i} = r.get16BitLookupTable()';
        end
        if ~isempty(colorMaps{s, i})
            newMap = colorMaps{s, i};
            m = newMap(row, col) < 0;
            newMap(m) = newMap(m) + bppMax;
            colorMaps{s, i} = newMap / (bppMax - 1);
        end

        % convert byte array to MATLAB image
        if isBioFormatsTrunk && (sgn || ~canTypecast)
            % can get the data directly to a matrix
            arr = loci.common.DataTools.makeDataArray2D(plane, ...
                bpp, fp, little, height);
        else
            % get the data as a vector, either because makeDataArray2D
            % is not available, or we need a vector for typecast
            arr = loci.common.DataTools.makeDataArray(plane, ...
                bpp, fp, little);
        end

        % Java does not have explicitly unsigned data types;
        % hence, we must inform MATLAB when the data is unsigned
        if ~sgn
            if canTypecast
                % TYPECAST requires at least MATLAB 7.1
                % NB: arr will always be a vector here
                switch class(arr)
                    case 'int8'
                        arr = typecast(arr, 'uint8');
                    case 'int16'
                        arr = typecast(arr, 'uint16');
                    case 'int32'
                        arr = typecast(arr, 'uint32');
                    case 'int64'
                        arr = typecast(arr, 'uint64');
                end
            else
                % adjust apparent negative values to actual positive ones
                % NB: arr might be either a vector or a matrix here
                mask = arr < 0;
                adjusted = arr(mask) + bppMax / 2;
                switch class(arr)
                    case 'int8'
                        arr = uint8(arr);
                        adjusted = uint8(adjusted);
                    case 'int16'
                        arr = uint16(arr);
                        adjusted = uint16(adjusted);
                    case 'int32'
                        arr = uint32(arr);
                        adjusted = uint32(adjusted);
                    case 'int64'
                        arr = uint64(arr);
                        adjusted = uint64(adjusted);
                end
                adjusted = adjusted + bppMax / 2;
                arr(mask) = adjusted;
            end
        end

        if isvector(arr)
            % convert results from vector to matrix
            shape = [width height];
            arr = reshape(arr, shape)';
        end

        % build an informative title for our figure
        label = id;
        if numSeries > 1
            qs = int2str(s);
            label = [label, '; series ', qs, '/', int2str(numSeries)];
        end
        if numImages > 1
            qi = int2str(i);
            label = [label, '; plane ', qi, '/', int2str(numImages)];
            if r.isOrderCertain()
                lz = 'Z';
                lc = 'C';
                lt = 'T';
            else
                lz = 'Z?';
                lc = 'C?';
                lt = 'T?';
            end
            zct = r.getZCTCoords(i - 1);
            sizeZ = r.getSizeZ();
            if sizeZ > 1
                qz = int2str(zct(1) + 1);
                label = [label, '; ', lz, '=', qz, '/', int2str(sizeZ)];
            end
            sizeC = r.getSizeC();
            if sizeC > 1
                qc = int2str(zct(2) + 1);
                label = [label, '; ', lc, '=', qc, '/', int2str(sizeC)];
            end
            sizeT = r.getSizeT();
            if sizeT > 1
                qt = int2str(zct(3) + 1);
                label = [label, '; ', lt, '=', qt, '/', int2str(sizeT)];
            end
        end

        % save image plane and label into the list
        imageList{i, 1} = arr;
        imageList{i, 2} = label;
    end

    % extract metadata table for this series
    metadataList = r.getMetadata();

    % save images and metadata into our master series list
    result{s, 1} = imageList;
    result{s, 2} = metadataList;
    result{s, 3} = colorMaps;
    fprintf('\n');
end
r.close();
toc

% -- Helper functions --

function [result] = versionCheck(v, maj, min)

tokens = regexp(v, '[^\d]*(\d+)[^\d]+(\d+).*', 'tokens');
majToken = tokens{1}(1);
minToken = tokens{1}(2);
major = str2num(majToken{1});
minor = str2num(minToken{1});
result = major > maj || (major == maj && minor >= min);
