Code covered by the BSD License  

Highlights from
Dicom Import GUI

image thumbnail
from Dicom Import GUI by Christian Wuerslin
Sorts DICOM files into series and lets you select the series you want to load in a GUI

DicomImport(sPath, lRecursive, csAdditionalTags)
function SDataOut = DicomImport(sPath, lRecursive, csAdditionalTags)
% DICOMIMPORT Import DICOM series with preview.
%
%   SDATA = DICOMIMPORT(sPATH, lRECURSIVE) Scans the folder specified by
%   sPATH for DICOM files. If lRECURSIVE is true, DICOMIMPORT
%   hierarchically scans all subolders of sPATH. Valid DICOM files are
%   sorted into series according the DICOM tag SeriesInstanceUID and the
%   folder in which the files were found. For each series, a thumbnail
%   image is created and an overview of the obtained series is displayed in
%   a GUI that lets you select the series to load. Select series as if you
%   would in a file browser by klicking and using the SHIFT and CNTL keys
%   to select multiple series Close the GUI by pressing <ENTER> or <ESCAPE>
%   (the latter returns an empty array). Output SDATA is a struct where
%   length(SDATA) equals the amout of selected series. SDATA contains at
%   least the following fields:
%
%       Img:                The image volume
%       SeriesDescription:  The contents of the corresponding DICOM tag
%       Orientation:        The most prominent image volume orientation
%       Aspect:             The pixel spacing in all 3 directions
%       ImageOrientation:   The direction cosines (compare DICOM standard)
%       ImagePosition:      The coordinates of the upper left corner of
%                           each image slice (compare DICOM standard)
%
%   The tags specified in csADDITIONALTAGS (see below) are appended to this
%   structure.
%
%
%   DICOMIMPORT lets you select the base folder in a dialog and asks if the
%   sobfolders are to be scanned.
%
%   DICOMIMPORT(sPATH, lRECURSIVE, csADDITIONALTAGS) Lets you specify
%   additional DICOM tags you can use for further processing in the cell
%   array of strings csADDITIONALTAGS
%
%
% Copyright 2013 Christian Wuerslin, University of Tuebingen and
% University of Stuttgart, Germany.
% Contact: christian.wuerslin@med.uni-tuebingen.de

% =========================================================================
% *** FUNCTION DicomImport
% ***
% *** Main GUI function. See above for description.
% ***
% =========================================================================

% -------------------------------------------------------------------------
% Control the figure's appearence
SAp.sTITLE              = 'Dicom Import [press enter to confirm]';
SAp.iTHUMBNAILSIZE      = 200;
SAp.iINFOBARHEIGHT      = 125;
SAp.dBGCOLOR            = [0.1 0.2 0.3];
SAp.iNROWS              = 2;
SAp.iNCOLS              = 4;
SAp.iTITLEHEIGHT        = 16;
SAp.dOPACITY            = 0.5;
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Define a list of DICOM tags to read (those that I really need)
csTags = {'SeriesInstanceUID',       'Rows',                 'Columns', ...
          'ImageOrientationPatient', 'ImagePositionPatient', 'PixelSpacing', ...
          'SeriesDescription', 'Modality'};
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Parse input arguments
if nargin
    if ~ischar(sPath), error('Input argument must be a string!'); end
else
    sPath = uigetdir;
    if isnumeric(sPath)
        SDataOut = [];
        return
    end
end

if nargin < 2, lRecursive = strcmp(questdlg('Search subdirectories for DICOM files?', 'Stupid question', 'Yup', 'Noop', 'Yup'), 'Yup'); end
if nargin > 2, csTags = [csTags, csAdditionalTags]; end
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Get all DICOM files and their headers
fprintf(1, 'Scanning for DICOM files...');
SData = fGetDicomFiles(sPath, csTags, lRecursive);
fprintf(1, 'done.\n');
if isempty(SData), return, end
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Sort the series into a struct and get a thumbnail of each set
fprintf(1, 'Creating thumbnails...');
iHashs = [SData.iHash];
iSeriesHash = fUnique(iHashs);
iNDatasets = length(iSeriesHash);
iThumbnails = zeros(SAp.iTHUMBNAILSIZE, SAp.iTHUMBNAILSIZE, iNDatasets);
for iDataset = 1:iNDatasets
    iThisSeries = find([SData.iHash] == iSeriesHash(iDataset));
    SSeries(iDataset).SData = SData(iThisSeries);

    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Load the center image and create thumbnail
    iInd = iThisSeries(round(length(iThisSeries)./2));
    dImg = double(dicomread(SData(iInd).sFilename));
    if isempty(dImg), continue, end
    
    dImg = dImg - min(dImg(:));
    dX = linspace(1, length(dImg), SAp.iTHUMBNAILSIZE);
    [dYY, dXX] = meshgrid(dX, dX);
    dImg = interp2(dImg, dYY, dXX, 'linear*', 0);
    if max(dImg(:)), dImg = dImg./max(dImg(:)); end
    iThumbnails(:,:,iDataset) = dImg;
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
end
lSelected = false(iNDatasets, 1);
fprintf(1, 'done.\n');
fprintf(1, 'Found %u files in %u datasets!\n', length(SData), iNDatasets);
clear iDataset iThisSeries iInd dImg dX dXX dYY
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Create the figure.
dFigureWidth  = (SAp.iTHUMBNAILSIZE + 1).*SAp.iNCOLS;
dFigureHeight = (SAp.iTHUMBNAILSIZE + SAp.iTITLEHEIGHT + 1).*SAp.iNROWS + SAp.iINFOBARHEIGHT + 2;
dScreenSize = get(0, 'MonitorPositions');
try
    hF = figure(...
        'Position'             , [(dScreenSize(3) - dFigureWidth )./2, ...
        (dScreenSize(4) - dFigureHeight)./2, ...
        dFigureWidth, dFigureHeight], ...
        'Units'                , 'pixels', ...
        'Color'                , SAp.dBGCOLOR, ...
        'Resize'               , 'off', ...
        'DockControls'         , 'off', ...
        'MenuBar'              , 'none', ...
        'Name'                 , SAp.sTITLE, ...
        'NumberTitle'          , 'off', ...
        'BusyAction'           , 'cancel', ...
        'KeyPressFcn'          , @fKeyPressFcn, ...
        'CloseRequestFcn'      , @fCloseGUI, ...
        'WindowButtonDownFcn'  , @fWindowButtonDownFcn, ...
        'WindowButtonMotionFcn', @fWindowMouseMoveFcn, ...
        'WindowScrollWheelFcn' , @fWindowScrollWheelFcn);
catch %#ok<CTCH>
    hF = figure(...
        'Position'             , [(dScreenSize(3) - dFigureWidth )./2, ...
        (dScreenSize(4) - dFigureHeight)./2, ...
        dFigureWidth, dFigureHeight], ...
        'Units'                , 'pixels', ...
        'Color'                , SAp.dBGCOLOR, ...
        'Resize'               , 'off', ...
        'DockControls'         , 'off', ...
        'MenuBar'              , 'none', ...
        'Name'                 , SAp.sTITLE, ...
        'NumberTitle'          , 'off', ...
        'BusyAction'           , 'cancel', ...
        'KeyPressFcn'          , @fKeyPressFcn, ...
        'CloseRequestFcn'      , @fCloseGUI, ...
        'WindowButtonDownFcn'  , @fWindowButtonDownFcn, ...
        'WindowButtonMotionFcn', @fWindowMouseMoveFcn);
end
clear dScreenSize
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Create the axes
hAxes = zeros(SAp.iNCOLS.*SAp.iNROWS, 1);
hImg  = zeros(SAp.iNCOLS.*SAp.iNROWS, 1);
hText = zeros(SAp.iNCOLS.*SAp.iNROWS, 1);
for iM = 1:SAp.iNROWS
    for iN = 1:SAp.iNCOLS
        iLinInd = (iM - 1).*SAp.iNCOLS + iN;
        dVertPos = dFigureHeight - iM.*(SAp.iTHUMBNAILSIZE + SAp.iTITLEHEIGHT + 1) + 1;
        iPosition = [(iN - 1).*(SAp.iTHUMBNAILSIZE + 1) + 2, dVertPos, SAp.iTHUMBNAILSIZE, SAp.iTHUMBNAILSIZE];
        hAxes(iLinInd) = axes('Parent' , hF, 'Units', 'pixels', 'Position', iPosition);
        hImg(iLinInd) = image(zeros(SAp.iTHUMBNAILSIZE, SAp.iTHUMBNAILSIZE, 3), 'Parent', hAxes(iLinInd));
        hText(iLinInd) = uicontrol(...
            'Parent'            , hF, ...
            'Style'             , 'text', ...
            'Units'             , 'pixels', ...
            'Position'          , [iPosition(1), iPosition(2) + iPosition(4), SAp.iTHUMBNAILSIZE, SAp.iTITLEHEIGHT], ...
            'BackgroundColor'   , SAp.dBGCOLOR./2, ...
            'ForegroundColor'   , 'w', ...
            'HorizontalAlign'   , 'left', ...
            'FontUnits'         , 'pixels', ...
            'FontSize'          , 12);
    end
end
axis(hAxes, 'off');
clear iM iN
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Create the info text objects
uicontrol(...
    'Parent'            , hF, ...
    'Style'             , 'text', ...
    'Units'             , 'pixels', ...
    'Position'          , [2, 2, 200, SAp.iINFOBARHEIGHT], ...
    'BackgroundColor'   , SAp.dBGCOLOR./2, ...
    'ForegroundColor'   , 'w', ...
    'HorizontalAlign'   , 'left', ...
    'FontUnits'         , 'pixels', ...
    'FontSize'          , 14, ...
    'String'            , {'Series Description:', 'Path:', 'Modality:', 'Rows:', 'Columns:', 'Images:', 'Pixel Spacing:'});
hInfoText = uicontrol(...
    'Parent'            , hF, ...
    'Style'             , 'text', ...
    'Units'             , 'pixels', ...
    'Position'          , [202, 2, dFigureWidth - 202, SAp.iINFOBARHEIGHT], ...
    'BackgroundColor'   , SAp.dBGCOLOR./2, ...
    'ForegroundColor'   , 'w', ...
    'HorizontalAlign'   , 'left', ...
    'FontUnits'         , 'pixels', ...
    'FontSize'          , 14, ...
    'String'            , '');
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Create a beatuiful mask for the selection highlight
dMask = repmat(permute(SAp.dBGCOLOR, [1 3 2]) , [SAp.iTHUMBNAILSIZE, SAp.iTHUMBNAILSIZE, 1]);
dMask = dMask.*repmat(linspace(1, 0.5, SAp.iTHUMBNAILSIZE)', [1, SAp.iTHUMBNAILSIZE, 3]);
dMask = dMask + 0.05.*rand(size(dMask));
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Fill the figure
iStartSeries = 1;
iLastSeries = 0;
sResult = 'esc';
fFillPanels();
% -------------------------------------------------------------------------

uiwait(hF);
% -------------------------------------------------------------------------
% ~~~~~~~~~~~~ A lot of user-friendly GUI interaction going on ~~~~~~~~~~~~
% -------------------------------------------------------------------------


% The GUI was closed in some way, continue execution


% -------------------------------------------------------------------------
% Dialog was aborted: Seriously, who does that?
if strcmp(sResult, 'esc')
    SDataOut = [];
    try delete(hF); end
    return
end
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% No seriers selected? - Return
iInd = find(lSelected);
if isempty(iInd)
    SDataOut = [];
    delete(hF);
    return
end
% -------------------------------------------------------------------------

% -------------------------------------------------------------------------
% Load the selected series
fprintf(1, 'Loading slected DICOM files...');
for i = 1:length(iInd)
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Load the specified images
    SThisData = SSeries(iInd(i)).SData;
    iNImages = length(SThisData);
    iImg = zeros(SThisData(1).Rows, SThisData(1).Columns, iNImages, 'uint16');
    for j = 1:iNImages
        iImg(:,:,j) = dicomread(SThisData(j).sFilename);
    end
    % Now images are in iImg, headers in SThisData
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Find out the major orientation
    dImageOrientation = reshape(SThisData(1).ImageOrientationPatient, [3, 2])'; % Should be the same for a volume
    dImagePosition = [SThisData.ImagePositionPatient]';
    
    dOrientIndicator = sum(abs(dImageOrientation));
    [temp, iMinInd] = min(dOrientIndicator); %#ok<ASGLU>
    d3rdDimInd = dImagePosition(:, iMinInd);
    [temp, iSortInd] = sort(d3rdDimInd, 'ascend'); %#ok<ASGLU>
    iImg = iImg(:,:,iSortInd);
    iImageOrientation = round(dImageOrientation);
    switch(iMinInd)
        case 1 % The x coordinate changes least in both directions -> Sagittal
            SDataOut(i).Orientation = 'Sag'; % -> x: a->p=pos, y: h->f=neg
            if iImageOrientation(1, 2) == -1, flipdim(iImg, 2); end % <- y-coordinate was flipped
            if iImageOrientation(2, 3) ==  1, flipdim(iImg, 1); end % <- z-coordinate was flipped
            
        case 2 % The y coordinate changes least in both directions -> Coronal
            SDataOut(i).Orientation = 'Cor'; % -> x: r->l=pos, y: h->f=neg
            if iImageOrientation(1, 1) == -1, flipdim(iImg, 2); end % <- x-coordinate was flipped
            if iImageOrientation(2, 3) ==  1, flipdim(iImg, 1); end % <- z-coordinate was flipped
            
        case 3 % The z coordinate changes least in both directions -> Transversal
            SDataOut(i).Orientation = 'Tra'; % -> x: r->l=pos, y: a->p=pos
            if iImageOrientation(1, 1) == -1, flipdim(iImg, 2); end % <- x-coordinate was flipped
            if iImageOrientation(2, 2) == -1, flipdim(iImg, 1); end % <- Y-coordinate was flipped
    end
    SDataOut(i).Img = iImg;
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    % Save the most important infos to the ouput variable
    SDataOut(i).SeriesDescriptions = SThisData(1).SeriesDescription;
    SDataOut(i).Aspect = zeros(3, 1);
    SDataOut(i).Aspect(1:2) = SThisData(1).PixelSpacing;
    SDataOut(i).Aspect(3)   = abs(d3rdDimInd(2) - d3rdDimInd(1));
    SDataOut(i).ImageOrientation = dImageOrientation;
    SDataOut(i).ImagePosition = dImagePosition(iSortInd, :);
    if nargin == 3
        for j = 1:length(csAdditionalTags) % The additionally requested infos
            sTag = csAdditionalTags{j};
            eval(['xData = [SThisData.', sTag, ']'';']);
            xData = xData(iSortInd);
            eval(['SDataOut(i).', sTag, ' = xData;']);
        end
    end
    % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
end
% -------------------------------------------------------------------------
fprintf(1, 'done.\n');

delete(hF);
% =========================================================================
% ***
% *** The 'end' of the DICOMIMPORT main function.
% ***
% =========================================================================


 
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * NESTED FUNCTION fCloseGUI (nested in DicomImport)
    % * * 
    % * * Figure callback
    % * *
    % * * Closes the figure.
    % * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function fCloseGUI(hObject, eventdata) %#ok<*INUSD> eventdata is repeatedly unused
        uiresume(hObject);
        delete(hObject);
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fCloseGUI
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
    
    
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * NESTED FUNCTION fWindowMouseMoveFcn (nested in DicomImport)
    % * * 
    % * * Figure callback
    % * *
    % * * Displays informations about the series under the mouse cursor in
    % * * the texts at the bottom of the figure.
    % * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function fWindowMouseMoveFcn(hObject, eventdata)
        if ~exist('hImg', 'var'), return, end; % Return if called during GUI startup
        
        iAxisInd = fGetAxes();
        iSeriesInd = iAxisInd + iStartSeries -1;
        
        % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        % Return if not over axes or out of data bounds
        if ~iAxisInd, set(hInfoText, 'String', ''); return, end
        if iSeriesInd > length(lSelected),  set(hInfoText, 'String', ''); return, end
        % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
        % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        % Print Information
        sText = SSeries(iSeriesInd).SData(1).SeriesDescription;
        if isempty(sText), sText = 'N/A'; end, csText(1) = {sText};
        
        sText = fileparts(SSeries(iSeriesInd).SData(1).sFilename);
        csText(2) = {sText};
        
        sText = SSeries(iSeriesInd).SData(1).Modality;
        if isempty(sText), sText = 'N/A'; end, csText(3) = {sText};
        
        sText = SSeries(iSeriesInd).SData(1).Rows;
        if isempty(sText), sText = 'N/A'; end, csText(4) = {sText};
        
        sText = SSeries(iSeriesInd).SData(1).Columns;
        if isempty(sText), sText = 'N/A'; end, csText(5) = {sText};
        
        sText = num2str(length(SSeries(iSeriesInd).SData));
        if isempty(sText), sText = 'N/A'; end, csText(6) = {sText};
        
        dPixelSpacing =  SSeries(iSeriesInd).SData(1).PixelSpacing;
        if ~isempty(dPixelSpacing)
            sText = sprintf('%2.2f x %2.2f mm', dPixelSpacing(1), dPixelSpacing(2));
        else
            sText = 'N/A';
        end
        csText(7) = {sText};
        
        set(hInfoText, 'String', csText);
        % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fWindowMouseMoveFcn
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

    
    
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * NESTED FUNCTION fWindowButtonDownFcn (nested in DicomImport)
    % * * 
    % * * Figure callback
    % * *
    % * * Starting callback for mouse button actions. Manage the selection
    % * * of the series.
    % * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function fWindowButtonDownFcn(hObject, eventdata)
        iPanelInd = fGetAxes();
        iSeriesInd = iPanelInd + iStartSeries - 1;
        
        if ~iPanelInd, return, end % Exit if Event didn't occurr in axes
        if iSeriesInd > length(lSelected), return, end

        switch get(hF, 'SelectionType')
            
            % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            % Left mouse button without any key
            case 'normal'
                iNSelected = sum(lSelected);
                lState = lSelected(iSeriesInd);
                lSelected = false(size(lSelected));
                lSelected(iSeriesInd) = ~lState || iNSelected > 1;
                if ~lState, iLastSeries = iSeriesInd; end
            % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                
            % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            % Right mouse button/shift key
            case 'extend'
                if iLastSeries
                    iInd = sort([iLastSeries, iSeriesInd], 'ascend');
                    lSelected(iInd(1):iInd(2)) = true;
                else
                    lSelected(iSeriesInd) = true;
                    iLastSeries = iSeriesInd;
                end
            % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            
            % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            % Middle mouse button/alt key
            case 'alt'
                lState = lSelected(iSeriesInd);
                lSelected(iSeriesInd) = ~lState;
                if ~lState, iLastSeries = iSeriesInd; end
            % - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                
        end
        fFillPanels;
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fWindowButtonDownFcn
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
    
    
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * NESTED FUNCTION fKeyPressFcn (nested in DicomImport)
    % * * 
    % * * Figure callback
    % * *
    % * * Callback for keyboard actions.
    % * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function fKeyPressFcn(hObject, eventdata) %#ok<INUSL>
    
        switch eventdata.Key

            case 'uparrow'
                iStartSeries = max([1, iStartSeries - SAp.iNCOLS]);
                fFillPanels;

            case 'downarrow'
                if iStartSeries + SAp.iNCOLS < length(lSelected),
                    iStartSeries = iStartSeries + SAp.iNCOLS;
                end
                
            case 'return' % Return and load the selected series
                sResult = 'OK';
                uiresume(hF);
                    
            case 'escape' % Return and do nothing
                uiresume(hF);
                
        end % switch
        
        fFillPanels;
        fWindowMouseMoveFcn(hF, []); % Make sure the info at the bottom is valid

    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fKeyPressFcn
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
       
    
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * NESTED FUNCTION fWindowScrollWheelFcn (nested in DicomImport)
    % * * 
    % * * Figure callback
    % * *
    % * * Callback for keyboard actions.
    % * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function fWindowScrollWheelFcn(hObject, eventdata)
        if eventdata.VerticalScrollCount > 0
            SEData.Key = 'downarrow';
        else
            SEData.Key = 'uparrow';
        end
        fKeyPressFcn(hObject, SEData); % This is the laszy way: just call the other callback
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fWindowScrollWheelFcn
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
    
       
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * *
    % * * NESTED FUNCTION fFillPanels (nested in DicomImport)
    % * *
    % * * Display the current data in all axes.
	% * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function fFillPanels()
        for iI = 1:length(hImg);
            iSeriesInd = iI + iStartSeries - 1;
            if iSeriesInd > length(lSelected)
                set(hImg(iI), 'CData', dMask./3);
                set(hText(iI), 'String', '<no data>');
            else
                dImg = repmat(iThumbnails(:,:,iSeriesInd), [1 1 3]);
                if lSelected(iSeriesInd),  dImg = 1 - ((1 - dImg).*(1 - SAp.dOPACITY.*dMask)); end
                set(hImg(iI), 'CData', dImg);
                sTitle = SSeries(iSeriesInd).SData(1).SeriesDescription;
                if length(sTitle) > 22, sTitle = [sTitle(1:21), '...']; end
                if isempty(sTitle), sTitle = '<no description>'; end
                set(hText(iI), 'String', sprintf('[%u]: %s', iSeriesInd, sTitle));
            end
        end
        drawnow expose
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fFillPanels
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
    
    
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * *
    % * * NESTED FUNCTION fGetAxes (nested in DicomImport)
    % * *
    % * * Determine the axes number under the mouse cursor. Returns 0 if
    % * * not over an axes at all.
	% * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function iPanelInd = fGetAxes()
        iCursorPos = get(hF, 'CurrentPoint');
        iPanelInd = uint8(0);
        for iI = 1:length(hAxes)
            dPos = get(hAxes(iI), 'Position');
            if ((iCursorPos(1) >= dPos(1)) && (iCursorPos(1) < dPos(1) + dPos(3)) && ...
                (iCursorPos(2) >= dPos(2)) && (iCursorPos(2) < dPos(2) + dPos(4)))
                iPanelInd = uint8(iI);
            end
        end
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fGetAxes
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
    
    
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * *
    % * * NESTED FUNCTION fGetDicomFiles (nested in DicomImport)
    % * *
    % * * Finds and creates a database of DICOM files
	% * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function SD = fGetDicomFiles(sFolder, csTags, lRecursive)
        SD = [];
        SDir = dir(sFolder);
        for iI = 1:length(SDir)
            sName = SDir(iI).name;
                        
            if (SDir(iI).isdir) && (lRecursive)
                if sName(1) == '.', continue, end
                SD = [SD, fGetDicomFiles([sFolder, filesep, SDir(iI).name], csTags, lRecursive)];
            else
                sFilename = [sFolder, filesep, SDir(iI).name];
                try
                    SHeader = dicominfo(sFilename);
                catch %#ok<CTCH>
                    continue
                end
                SRecord = [];
                for iJ = 1:length(csTags)
                    sTag = csTags{iJ};
                    try
                        eval(['SRecord.', sTag, ' = SHeader.', sTag, ';']);
                    catch %#ok<CTCH>
                        eval(['SRecord.', sTag, ' = [];']);
                    end
                end
                if isfield(SHeader, 'SeriesInstanceUID')
                    SRecord.iHash = fHash([sFolder, SHeader.SeriesInstanceUID]);
                    SRecord.sFilename = sFilename;
                    SD = [SD, SRecord];
                end
            end
        end
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fGetDicomFiles
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =



    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * *
    % * * NESTED FUNCTION fHash (nested in DicomImport)
    % * *
    % * * Return a hash number
	% * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function iHashVal = fHash(sString)
        dString = double(sString);
        dHashVal = 5381;
        for iI = 1:length(dString)
            dHashVal = mod(dHashVal * 33 + dString(iI), 2.^32 - 1);
        end
        iHashVal = uint32(dHashVal);
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fHash
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
    
    
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * *
    % * * NESTED FUNCTION fUnique (nested in DicomImport)
    % * *
    % * * Returns unique values in order of appearance
	% * *
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    function xUniqueVals = fUnique(xArray)
        xUniqueVals = zeros(length(xArray), 1, class(xArray));
        iInd = 0;
        while ~isempty(xArray)
            iInd = iInd + 1;
            xUniqueVals(iInd) = xArray(1);
            xArray(xArray == xArray(1)) = [];
        end
        xUniqueVals = xUniqueVals(1:iInd);
    end
    % = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    % * * END NESTED FUNCTION fUnique
	% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

end

Contact us