unit PicViewMain;
// ------------------------------------------------
// PICViewer - BioRad PIC file viewer
// (c) J. Dempster, University of Strathclyde, 2003
// ------------------------------------------------
// V1.0 25.3.03
// V1.3 8.4.03 Specific PIC Panel file can now be closed
// V1.4 11.4.03 Green, red and blue scale display LUT mapping added
//              PICVIEWER.CLOSEPIC command now works correctly
// V1.5 24.4.03 Copy to clipboard facilty added
// V1.6 6.5.03  Support reading/writing STK and TIF added
// V1.7
// V1.8 16.9.03 Line ROI added, inverted ROIs supported correctly
// V1.9 3.11.03 Z Projection and Merge to RGB added
// V2.0 17.02.04 PicViewer can now open a file specified in parameter string
//               Merge OK button now re-enabled after merge
// V2.1 26.04.04 Merge files facility added
// V2.2 11.03.04 Movie forward/reverse playback added
// V2.3 22.03.04 Merge files now sorted into ascending alphabetic order
// V2.4 24.05.04 Z and inter-frame time interval now saved in PIC files
// V2.4.1 27.05.04 Various bugs fixed
// V2.4.1 31.05.04
// V2.4.2 27.07.04 No. of detectable objects now 1000/frame
// V2.4.4 03.09.04 Files now merged in true numeric rather than alphabetic order
// V2.4.5 03.09.04 TestStack added
// V2.4.8 28.03.05 Save As .. updated extensions now forced to be same as file list
// V2.4.9 13.11.05 Bug which prevent GB merge in RGB merge fixed
//                 Graph plot cursors now integrated into figure
// V2.5.0 23.06.06 Merge files can now be sorted alphabetically or by trailing index no.
// V2.5.1 11.12.06 RGB files can now be imported
//                 RGB channels can now be split into separate gray scale file
// V2.5.2 25.06.09 Merging of multiple files sorted by index number no longer limited
//                 to c. 300 files. New import unit added (mergefileunit.pas)
//                 Display zoom between 25% % 600% now possible along with scrolling of image

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Menus, ComCtrls, ViewUnit, ImageFile, ResultsUnit, GraphUnit, Math ;

const
   DataFileExtension = 'PIC' ;
   TrackFileExtension = 'TRK' ;

   MergeByIndex = 0 ;
   MergeAlphabetic = 1 ;

type


  TMainFrm = class(TForm)
    MainMenu1: TMainMenu;
    mnFile: TMenuItem;
    mnOpen: TMenuItem;
    N1: TMenuItem;
    mnExit: TMenuItem;
    StatusBar: TStatusBar;
    OpenDialog: TOpenDialog;
    mnSaveAs8Bit: TMenuItem;
    mnEdit: TMenuItem;
    mnCopyData: TMenuItem;
    mnCopyImage: TMenuItem;
    mnPrint: TMenuItem;
    mnPrintSetup: TMenuItem;
    mnSaveAs16Bit: TMenuItem;
    SaveDialog: TSaveDialog;
    mnAnalysis: TMenuItem;
    mnZProjection: TMenuItem;
    mnMergeToRGB: TMenuItem;
    mnWindows: TMenuItem;
    N2: TMenuItem;
    PrinterSetupDialog: TPrinterSetupDialog;
    MergeFile: TImageFile;
    N3: TMenuItem;
    mnMergeFiles: TMenuItem;
    ImageFile: TImageFile;
    mnParticleDetection: TMenuItem;
    N4: TMenuItem;
    mnCreateAVI: TMenuItem;
    mnRatio: TMenuItem;
    mnPlotGraph: TMenuItem;
    mnSaveTable: TMenuItem;
    Help1: TMenuItem;
    mnContents: TMenuItem;
    mnSearch: TMenuItem;
    mnTestFile: TMenuItem;
    N5: TMenuItem;
    mnOpenTrackFile: TMenuItem;
    mnMergeByAlphabetic: TMenuItem;
    mnSortedNumerically: TMenuItem;
    mnSeparateRGB: TMenuItem;
    procedure mnOpenClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure mnExitClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure mnEditClick(Sender: TObject);
    procedure mnCopyDataClick(Sender: TObject);
    procedure mnCopyImageClick(Sender: TObject);
    procedure mnFileClick(Sender: TObject);
    procedure mnSaveAs8BitClick(Sender: TObject);
    procedure mnSaveAs16BitClick(Sender: TObject);
    procedure mnZProjectionClick(Sender: TObject);
    procedure mnAnalysisClick(Sender: TObject);
    procedure mnMergeToRGBClick(Sender: TObject);
    procedure mnPrintClick(Sender: TObject);
    procedure mnPrintSetupClick(Sender: TObject);
    procedure mnParticleDetectionClick(Sender: TObject);
    procedure mnCreateAVIClick(Sender: TObject);
    procedure mnRatioClick(Sender: TObject);
    procedure mnPlotGraphClick(Sender: TObject);
    procedure mnSaveTableClick(Sender: TObject);
    procedure mnContentsClick(Sender: TObject);
    procedure mnSearchClick(Sender: TObject);
    procedure mnHelpHowToClick(Sender: TObject);
    procedure mnTestFileClick(Sender: TObject);
    procedure mnOpenTrackFileClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure mnMergeByAlphabeticClick(Sender: TObject);
    procedure mnSortedNumericallyClick(Sender: TObject);
    procedure mnSeparateRGBClick(Sender: TObject);
  private
    { Private declarations }
    procedure MergeFiles( SortMode : Integer ) ;
    procedure TestStack ;

  public
    { Public declarations }
    ProgramDirectory : String ;           // Folder containing EXE file
    DataDirectory : String ;              // Last used data folder
    DefaultDataDirectory : String ;       // Default data folder
    // Image viewing forms in use
    ViewFrms : Array[0..99] of TViewFrm ;
    ViewFrmsInUse : Array[0..99] of Boolean ;
    // Results table forms in use
    ResultsFrms : Array[0..99] of TResultsFrm ;
    ResultsFrmsInUse : Array[0..99] of Boolean ;
    GraphFrms : Array[0..99] of TGraphFrm ;
    GraphFrmsInUse : Array[0..99] of Boolean ;

    NewFileName : String ;

    // Printer printer page settings
    PrinterTopMargin : Single ;      // Top margin (cm)
    PrinterBottomMargin : Single ;   // Bottom margin (cm)
    PrinterLeftMargin : Single ;     // Left margin (cm)
    PrinterRightMargin : Single ;    // Right margin (cm)
    PrinterFontName : String ;       // Font name
    PrinterFontSize : Integer ;      // Font size (points)
    PrinterLineThickness : Integer ; // Line thickness (points)

    // Image default copy size (pixels)
    ImageCopyWidth : Integer ;
    ImageCopyHeight : Integer ;

    AUTOSelectedPIC : Integer ;

    function CreateNewViewFrm( FileName : String ) : Integer ;
    Procedure CloseViewFrm( FileName : String )  ;
    function CreateNewResultsFrm( FileName : String ) : Integer ;
    function CreateNewGraphFrm( FormCaption : String ) : Integer ;
  end;

var
  MainFrm: TMainFrm;

    function CompareFileNumber(
             List: TStringList;
             Index1, Index2: Integer): Integer;


implementation

uses ZProjUnit, MergeUnit, ParticleDetectionUnit, AVIUnit, RatioUnit,
  PlotROIUnit, RGBSeparateUnit, MergeFileUnit;
//uses ViewUnit;

{$R *.dfm}

procedure TMainFrm.FormShow(Sender: TObject);
// -----------------------------------------------
// Initialisations when program is first displayed
// -----------------------------------------------
var
     i : Integer ;
     TempBuf : Array[0..255] of char ;
     SysDrive : String ;

begin
     // Get path to program folder
     ProgramDirectory := ExtractFilePath(ParamStr(0)) ;

     Application.HelpFile := ProgramDirectory + 'Picviewer.HLP';

     // Find/create data directory "<sysdrive\Experiments"
     GetWindowsDirectory( TempBuf, High(TempBuf) ) ;
     SYSDrive := ExtractFileDrive(String(TempBuf)) ;
     DataDirectory := SYSDrive + '\Experiments\' ;
     if not DirectoryExists(DataDirectory) then begin
        if not CreateDir(DataDirectory) then
           MessageDlg( 'Cannot create ' + DataDirectory, mtWarning, [mbOK], 0 ) ;
        end ;
     DefaultDataDirectory := DataDirectory ;

     // Clear arrays which indicates forms which are in use
     for i := 0 to High(ViewFrms) do ViewFrmsInUse[i] := False ;
     for i := 0 to High(ResultsFrms) do ResultsFrmsInUse[i] := False ;
     for i := 0 to High(GraphFrms) do GraphFrmsInUse[i] := False ;

     // Default printer page settings
     MainFrm.PrinterTopMargin := 2.5 ;


     MainFrm.PrinterBottomMargin := 2.5 ;
     MainFrm.PrinterLeftMargin:= 2.5  ;
     MainFrm.PrinterRightMargin := 2.5 ;
     MainFrm.PrinterFontName := 'Arial' ;
     MainFrm.PrinterFontSize := 12 ;
     MainFrm.PrinterLineThickness := 1 ;
     MainFrm.ImageCopyWidth := 600  ;
     MainFrm.ImageCopyHeight := 400  ;

     { Enable Windows menu to show active MDI windows }
     WindowMenu := mnWindows ;

     // Open a file if one has been supplied in parameter string
     if FileExists(ParamStr(1)) then CreateNewViewFrm( ParamStr(1) ) ;

     end;


procedure TMainFrm.mnOpenClick(Sender: TObject);
// ---------------
// Open image file
// ---------------
begin

     OpenDialog.options := [ofPathMustExist] ;
     OpenDialog.DefaultExt := DataFileExtension ;

     if MainFrm.DataDirectory <> '' then OpenDialog.InitialDir := MainFrm.DataDirectory ;

     OpenDialog.Filter :=
     'BIORad PIC Files (*.PIC)|*.PIC|' +
     'TIFF Files (*.TIF)|*.TIF|' +
     'STK Files (*.STK)|*.STK' ;
     OpenDialog.Title := 'Open Data File ' ;

     if OpenDialog.Execute then begin
        CreateNewViewFrm( OpenDialog.FileName ) ;
        end ;

     end;


function TMainFrm.CreateNewViewFrm(
         FileName : String         // Base file name to be opened
           ) : Integer ;           // Returns index # into ViewFrms array
//  ---------------------------
// Open a new image view window
// ----------------------------
var
     i : Integer ;
begin

     // Find an empty ViewFrm slot and use it
     NewFileName :=  FileName ;

     // Update data directory
     DataDirectory := ExtractFileDir(NewFileName) ;

     i := 0 ;
     while (ViewFrmsInUse[i]) and (i < High(ViewFrmsInUse)) do inc(i) ;

     if i < High(ViewFrmsInUse) then begin
        ViewFrms[i] := TViewFrm.Create(Self) ;
        ViewFrms[i].Index := i ;
        ViewFrmsInUse[i] := True ;
        Result := ViewFrms[i].Index ;
        end
     else begin
        MainFrm.StatusBar.SimpleText := 'Maximum number of image windows exceeded!' ;
        Result := -1 ;
        end ;

     end ;


function TMainFrm.CreateNewResultsFrm( FileName : String ) : Integer ;
//  ------------------------------------------------
// Open a new results table (returning index number)
// -------------------------------------------------
var
     i : Integer ;
begin

     i := 0 ;
     while (ResultsFrmsInUse[i]) and (i < High(ResultsFrmsInUse)) do inc(i) ;

     if i < High(ResultsFrmsInUse) then begin
        ResultsFrms[i] := TResultsFrm.Create(Self) ;
        ResultsFrms[i].Index := i ;
        ResultsFrms[i].FileName := FileName ;
        ResultsFrms[i].Caption := 'Results: ' + FileName ;
        ResultsFrmsInUse[i] := True ;
        end
     else MainFrm.StatusBar.SimpleText := 'Maximum number of results windows exceeded!' ;

     Result := i ;

     end ;


function TMainFrm.CreateNewGraphFrm( FormCaption : String ) : Integer ;
//  -----------------------------------------------------
// Open a new graph display form (returning index number)
// ------------------------------------------------------
var
     i : Integer ;
begin

     i := 0 ;
     while (GraphFrmsInUse[i]) and (i < High(GraphFrmsInUse)) do inc(i) ;

     if i < High(GraphFrmsInUse) then begin
        GraphFrms[i] := TGraphFrm.Create(Self) ;
        GraphFrms[i].Index := i ;
        GraphFrms[i].Caption := FormCaption ;
        GraphFrmsInUse[i] := True ;
        end
     else MainFrm.StatusBar.SimpleText := 'Maximum number of graph windows exceeded!' ;

     Result := i ;

     end ;




procedure TMainFrm.CloseViewFrm(
          FileName : String
          )  ;
// ---------------------
// Close a PIC file
// ---------------------
var
     i : Integer ;
begin

     // Close all forms displaying this file name
     for i :=  0 to High(ViewFrmsInUse) do begin
         if ViewFrmsInUse[i] then begin
            if ViewFrms[i].FileName = FileName then begin
               ViewFrms[i].Close ;
               Application.ProcessMessages ;
               end ;
            end ;
         end ;

     end ;


procedure TMainFrm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
// ---------------------------------
//  Warn user that program is closing
//  ---------------------------------
begin
     if MessageDlg( 'Exit Program! Are you Sure? ', mtConfirmation,
        [mbYes,mbNo], 0 ) = mrYes then CanClose := True
                                  else CanClose := False ;
     end;


procedure TMainFrm.mnExitClick(Sender: TObject);
// -------------
// Close program
// -------------
begin
     Close ;
     end;


procedure TMainFrm.mnEditClick(Sender: TObject);
// --------------------------------
// Enable/disable edit menu options
// --------------------------------
begin

     mnCopyData.Enabled := False ;
     mnCopyImage.Enabled := False ;
     if MDIChildCount > 0 then begin
        if Pos('ViewFrm',ActiveMDIChild.Name) > 0 then begin
           mnCopyData.Enabled := TViewFrm(ActiveMDIChild).CopyDataAvailable ;
           mnCopyImage.Enabled := TViewFrm(ActiveMDIChild).CopyImageAvailable ;
           end
        else if Pos('GraphFrm',ActiveMDIChild.Name) > 0 then begin
           mnCopyData.Enabled := True ;
           mnCopyImage.Enabled := True ;
           end
        else if Pos('ResultsFrm',ActiveMDIChild.Name) > 0 then begin
           mnCopyData.Enabled := True ;
           end ;
        end ;
     end;


procedure TMainFrm.mnCopyDataClick(Sender: TObject);
// --------------------------------
// Copy numerical data to clipboard
// --------------------------------
begin
     if Pos('ViewFrm',ActiveMDIChild.Name) > 0 then
        TViewFrm(ActiveMDIChild).CopyDataToClipboard
     else if Pos('GraphFrm',ActiveMDIChild.Name) > 0 then
        TGraphFrm(ActiveMDIChild).CopyDataToClipboard
     else if Pos('ResultsFrm',ActiveMDIChild.Name) > 0 then
        TResultsFrm(ActiveMDIChild).CopyDataToClipboard ;

     end;


procedure TMainFrm.mnCopyImageClick(Sender: TObject);
// ---------------------------------
// Copy displayed image to clipboard
// ---------------------------------
begin
     if Pos('ViewFrm',ActiveMDIChild.Name) > 0 then
        TViewFrm(ActiveMDIChild).CopyImageToClipboard
     else if Pos('GraphFrm',ActiveMDIChild.Name) > 0 then
        TGraphFrm(ActiveMDIChild).CopyImageToClipboard ;

     end;


procedure TMainFrm.mnFileClick(Sender: TObject);
// --------------------------------
// Enable/disable file menu options
// --------------------------------
begin

     mnPrint.Enabled := False ;
     mnSaveAs8Bit.Enabled := False ;
     mnSaveAs16Bit.Enabled := False ;
     mnCreateAVI.Enabled := False ;
     mnSaveTable.Enabled := False ;

     if MDIChildCount > 0 then begin
        if Pos('ViewFrm',ActiveMDIChild.Name) > 0 then begin
           mnPrint.Enabled := TViewFrm(ActiveMDIChild).CopyImageAvailable ;
           mnSaveAs8Bit.Enabled := True ;
           mnSaveAs16Bit.Enabled := True ;
           mnCreateAVI.Enabled := True ;
           end ;
        if Pos('GraphFrm',ActiveMDIChild.Name) > 0 then begin
           mnPrint.Enabled := True ;
           end ;
        if Pos('ResultsFrm',ActiveMDIChild.Name) > 0 then begin
           mnSaveTable.Enabled := True ;
           end ;
        end ;
     end;


procedure TMainFrm.mnSaveAs8BitClick(Sender: TObject);
// ----------------------
// Save copy of data file
// ----------------------
begin

     // Quit if form is not an image form
     if Pos('ViewFrm',ActiveMDIChild.Name) <= 0 then Exit ;

     SaveDialog.options := [ofPathMustExist] ;
     SaveDialog.DefaultExt := DataFileExtension ;

     if DataDirectory <> '' then SaveDialog.InitialDir := DataDirectory ;

     SaveDialog.Filter :=
     'BIORad PIC Files (*.PIC)|*.PIC|' +
     'TIFF Files (*.TIF)|*.TIF|' +
     'STK Files (*.STK)|*.STK';
     SaveDialog.Title := 'Save Data File (8 bit)' ;

     if SaveDialog.Execute then begin
        case SaveDialog.FilterIndex of
          1 : SaveDialog.FileName := ChangeFileExt( SaveDialog.FileName, '.pic' ) ;
          2 : SaveDialog.FileName := ChangeFileExt( SaveDialog.FileName, '.tif' ) ;
          3 : SaveDialog.FileName := ChangeFileExt( SaveDialog.FileName, '.stk' ) ;
          end ;
        if FileExists(SaveDialog.FileName) then begin
           if MessageDlg(format('%s Exists!. Overwrite it?',[SaveDialog.FileName])
              ,mtConfirmation,[mbYes, mbNo], 0) = mrYes then
              TViewFrm(ActiveMDIChild).SaveToFile( SaveDialog.FileName, 8 ) ;
           end
        else begin
           TViewFrm(ActiveMDIChild).SaveToFile( SaveDialog.FileName, 8 ) ;
           end ;
        DataDirectory := ExtractFileDir(SaveDialog.FileName) ;
        end ;

     end;


procedure TMainFrm.mnSaveAs16BitClick(Sender: TObject);
// -----------------------------------------
// Save copy of data file with 16 bit pixels
// -----------------------------------------
begin

     // Quit if form is not an image form
     if Pos('ViewFrm',ActiveMDIChild.Name) <= 0 then Exit ;

     SaveDialog.options := [ofPathMustExist] ;
     SaveDialog.DefaultExt := DataFileExtension ;

     if DataDirectory <> '' then OpenDialog.InitialDir := DataDirectory ;

     SaveDialog.Filter :=
     'BIORad PIC Files (*.PIC)|*.PIC|' +
     'TIFF Files (*.TIF)|*.TIF|' +
     'STK Files (*.STK)|*.STK';
     SaveDialog.Title := 'Save Data File (16 bit)' ;

     if SaveDialog.Execute then begin
        case SaveDialog.FilterIndex of
          1 : SaveDialog.FileName := ChangeFileExt( SaveDialog.FileName, '.pic' ) ;
          2 : SaveDialog.FileName := ChangeFileExt( SaveDialog.FileName, '.tif' ) ;
          3 : SaveDialog.FileName := ChangeFileExt( SaveDialog.FileName, '.stk' ) ;
          end ;
        if FileExists(SaveDialog.FileName) then begin
           if MessageDlg(format('%s Exists!. Overwrite it?',[SaveDialog.FileName])
              ,mtConfirmation,[mbYes, mbNo], 0) = mrYes then
              TViewFrm(ActiveMDIChild).SaveToFile( SaveDialog.FileName, 16 ) ;
           end
        else begin
           TViewFrm(ActiveMDIChild).SaveToFile( SaveDialog.FileName, 16 ) ;
           end ;
        DataDirectory := ExtractFileDir(SaveDialog.FileName) ;
        end ;
     end;


procedure TMainFrm.mnZProjectionClick(Sender: TObject);
// ----------------------------
// Open Z projection dialog box
// ----------------------------
begin
     ZProjFrm.ShowModal ;
     end;

procedure TMainFrm.mnAnalysisClick(Sender: TObject);
// -------------------
// Image analysis menu
// -------------------
var
     i : Integer ;
     NumGreyScaleImages  : Integer ;
     NumStacks  : Integer ;
     NumFiles  : Integer ;
begin
     // Find number of open image view windows
     NumGreyScaleImages := 0 ;
     NumStacks := 0 ;
     NumFiles := 0 ;
     if MDIChildCount > 0 then begin
        for i :=  0 to High(MainFrm.ViewFrmsInUse) do
            if MainFrm.ViewFrmsInUse[i] then begin
               Inc(NumFiles) ;
               if MainFrm.ViewFrms[i].NumComponentsPerPixel = 1 then
                  Inc(NumGreyScaleImages) ;
               if MainFrm.ViewFrms[i].NumFrames > 1 then
                  Inc(NumStacks) ;
               end ;
        end ;

     mnZProjection.Enabled := False ;
     mnMergeToRGB.Enabled := False ;
     mnParticleDetection.Enabled := False ;
     mnRatio.Enabled :=  False ;
     mnPlotGraph.Enabled :=  False ;
     mnSeparateRGB.Enabled := False ;
     if NumStacks > 0 then mnZProjection.Enabled := True ;
     if NumGreyScaleImages > 0 then mnMergeToRGB.Enabled := True ;
     if NumFiles > NumGreyScaleImages then mnSeparateRGB.Enabled := True ;
     if NumFiles > 0 then  begin
        mnParticleDetection.Enabled := True ;
        mnRatio.Enabled := True ;
        mnPlotGraph.Enabled := True ;
        end ;

     end;


procedure TMainFrm.mnMergeToRGBClick(Sender: TObject);
// ----------------------------
// Open Merge to RGB dialog box
// ----------------------------
begin
     MergeFrm.ShowModal ;
     end;


procedure TMainFrm.mnPrintClick(Sender: TObject);
// ----------------------
// Print displayed image
// ----------------------
begin
     if Pos('ViewFrm',ActiveMDIChild.Name) > 0 then
        TViewFrm(ActiveMDIChild).Print
     else if Pos('GraphFrm',ActiveMDIChild.Name) > 0 then
        TGraphFrm(ActiveMDIChild).Print ;
     end;

     
procedure TMainFrm.mnPrintSetupClick(Sender: TObject);
// ----------------------------------
// Display printer setup dialog box
// ----------------------------------
begin
     PrinterSetupDialog.Execute ;
     end;


procedure TMainFrm.MergeFiles( SortMode : Integer ) ;
// --------------------------------------------
// Merge list of selected file into single file
// --------------------------------------------
var
   FileNum : Integer ;
   ImportFileName : String ;
   OutFileName : String ;
   iFrame : Integer ;           // Frame counter
   NumFramesImported : Integer ; // No. of frames imported
   PFrameBuf : Pointer ;        // Image frame buffer pointer
   i : Integer ;
   OutFileCreated : Boolean ;
   FileList : TStringList ;
begin

    FileList := Nil ;
    PFrameBuf := Nil ;

    // Setup Open Files dialog box
    OpenDialog.Filter :=
     'BIORad PIC Files (*.PIC)|*.PIC|' +
     'TIFF Files (*.TIF)|*.TIF|' +
     'STK Files (*.STK)|*.STK' ;

    OpenDialog.options := [ofPathMustExist,ofAllowMultiSelect] ;
    if MainFrm.DataDirectory <> '' then OpenDialog.InitialDir := MainFrm.DataDirectory ;
    OpenDialog.Title := 'Import File ' ;

    // Exit if no file name available
    if (not OpenDialog.Execute) or (OpenDialog.Files.Count <= 0) then Exit ;

    // Sort list into order of numeric component of file name
    FileList := TStringList.Create ;
    FileList.Sorted := False ;

    // Add selected files
    for FileNum := 0 to OpenDialog.Files.Count-1 do
        FileList.Add(OpenDialog.Files[FileNum]) ;

    if SortMode = MergeByIndex then begin
       // Sort files into ascending order of trailing number
       FileList.CustomSort( CompareFileNumber ) ;
       end
    else begin
       // Sort files alphabetically
       FileList.Sort ;
       end ;

    // Import frames from all files in list
    NumFramesImported := 0 ;
    OutFileCreated := False ;

    for FileNum := 0 to FileList.Count-1 do begin

        // Open file
        ImportFileName := FileList.Strings[FileNum] ;
        if not ImageFile.OpenFile( ImportFileName ) then begin
           MainFrm.StatusBar.SimpleText := 'MERGE FILES: Unable to open : ' + ImportFileName ;
           Break ;
           end ;

        // Create output file to hold images
        if not OutFileCreated then begin

           // Create merge file name
           ImportFileName := FileList.Strings[0] ;
           OutFileName := '' ;
           i := 1 ;
           While (ImportFileName[i] <> '.') and (i <= Length(ImportFileName)) do begin
              OutFileName := OutFileName + ImportFileName[i] ;
              Inc(i) ;
              end ;

           if SortMode = MergeByIndex then begin
              OutFileName := OutFileName + format('[Merged %d-%d]',
                                           [ Integer(FileList.Objects[0]),
                                             Integer(FileList.Objects[FileList.Count-1])]) ;
              end
           else begin
              OutFileName := OutFileName + format('[%d Merged]',[FileList.Count]) ;
              end ;

           // Create PIC file if source is gray scale, TIF if RGB
           if ImageFile.ComponentsPerPixel > 1 then OutFileName := OutFileName + '.tif'
                                               else OutFileName := OutFileName + '.pic' ;

           MergeFile.CreateFile( OutFileName,
                                 ImageFile.FrameWidth,
                                 ImageFile.FrameHeight,
                                 ImageFile.PixelDepth,
                                 ImageFile.ComponentsPerPixel,
                                 False ) ;

           // Get file calibration factors
           MergeFile.XResolution := ImageFile.XResolution ;
           MergeFile.YResolution := ImageFile.YResolution ;
           MergeFile.ZResolution := ImageFile.ZResolution ;
           MergeFile.TResolution := ImageFile.TResolution ;

           // Allocate frame buffer
           GetMem( PFrameBuf,MergeFile.NumBytesPerFrame ) ;

           OutFileCreated := True ;

           end
        else begin
           // Terminate merge if image properties change
           if (MergeFile.FrameWidth <> ImageFile.FrameWidth) or
              (MergeFile.FrameHeight <> ImageFile.FrameHeight) or
              (MergeFile.PixelDepth <> ImageFile.PixelDepth) or
              (MergeFile.ComponentsPerPixel <> ImageFile.ComponentsPerPixel) then begin
              MainFrm.StatusBar.SimpleText := 'MERGE FILES: Aborted at ' + ImportFileName ;
              ImageFile.CloseFile ;
              Break ;
              end ;
           end ;

        // Import frames from this file
        for iFrame := 1 to ImageFile.NumFrames do begin
           if ImageFile.LoadFrame( iFrame, PFrameBuf ) then begin
              Inc(NumFramesImported) ;
              MergeFile.SaveFrame( NumFramesImported, PFrameBuf ) ;
              MainFrm.StatusBar.SimpleText := format(
              'MERGE FILES: Copying %.3d/%.3d frames from %s to %s',
              [iFrame,
               ImageFile.NumFrames,
               ExtractFileName(ImportFileName),
               ExtractFileName(OutFileName)
               ]) ;
              end ;
           end ;

        // Close this import file
        ImageFile.CloseFile ;

        end ;

    // Close output file
    MergeFile.CloseFile ;

    if PFrameBuf <> Nil then FreeMem( PFrameBuf ) ;
    if FileList <> Nil then FileList.Free ;

    // Display file
    if NumFramesImported > 0 then CreateNewViewFrm( OutFileName ) ;

    end ;


function CompareFileNumber(
         List: TStringList;
         Index1, Index2: Integer): Integer;
// ---------------------------------------------------
// Sort callback function (for TStringList.CustomSort)
// Sorts into ascending order of trailing number
// ---------------------------------------------------
var
    s : String ;
    sNum : String ;
    i : Integer ;
    ErrCode : Integer ;
    Num : Array[1..2] of Integer ;
    iNum : Integer ;
    EndOfNumber : Integer ;
    StartOfNumber : Integer ;
    Done : Boolean ;
begin

    // Get numbers from List.Strings[Index1] & List.Strings[Index1]

    for iNum := 1 to 2 do begin

        if iNum = 1 then s := List.Strings[Index1]
                    else s := List.Strings[Index2] ;

        i := Length(s) ;
        EndOfNumber := 0 ;
        StartOfNumber := 1 ;
        Done := False ;
        While not Done do begin
           if (EndOfNumber = 0) then begin
              if s[i] in ['0'..'9'] then EndOfNumber := i ;
              end
           else begin
              if not (s[i]in ['0'..'9']) then begin
                 StartOfNumber := i + 1 ;
                 Done := True ;
                 end ;
              end ;
           Dec(i) ;
           if i <= 0 then Done := True ;
           end ;

        sNum := '' ;
        for i := StartOfNumber to EndOfNumber do sNum := sNum + s[i] ;
        if Length(sNum) > 0 then Val(sNum,Num[iNum],ErrCode) ;

        if iNum = 1 then List.Objects[Index1] := TObject(Num[iNum])
                    else List.Objects[Index2] := TObject(Num[iNum]) ;

        end ;

    if Num[1] < Num[2] then Result := -1
    else if Num[1] = Num[2] then Result := 0
    else Result := 1 ;

    end ;



procedure TMainFrm.mnParticleDetectionClick(Sender: TObject);
// --------------------------------------
// Display Particle Detection dialog box
// -------------------------------------
begin
     ParticleDetectionFrm.ShowModal ;
     end;

procedure TMainFrm.mnCreateAVIClick(Sender: TObject);
begin
     AVIFrm := TAVIFrm.Create(Self) ;
     end;

procedure TMainFrm.mnRatioClick(Sender: TObject);
// ----------------------------
// Open image ratio dialog box
// ----------------------------
begin
     RatioFrm.ShowModal ;
     end;

procedure TMainFrm.mnPlotGraphClick(Sender: TObject);
// ----------------------------
// Open ROI Plot dialog box
// ----------------------------
begin
     PlotROIFrm.ShowModal ;
     end;

procedure TMainFrm.mnSaveTableClick(Sender: TObject);
// ----------------------------------
// Save data in results table to file
// ----------------------------------
begin
     if ActiveMDIChild.Name = 'ResultsFrm' then begin
        TResultsFrm(ActiveMDIChild).SaveTableToFile ;
        end ;
     end;

procedure TMainFrm.mnContentsClick(Sender: TObject);
// --------------------------
// Display help contents list
// --------------------------
begin
     application.helpcommand( HELP_CONTENTS, 0 ) ;
     end;

procedure TMainFrm.mnSearchClick(Sender: TObject);
// ------------------
// Search for help on
// ------------------
begin
     application.helpcommand( HELP_PARTIALKEY, 0 ) ;

     end;

procedure TMainFrm.mnHelpHowToClick(Sender: TObject);
// ---------------------
// How to use help files
// ---------------------
begin
     application.helpcommand( HELP_HELPONHELP, 0 ) ;
     end;


procedure TMainFrm.TestStack ;
// --------------------------------------------
// Create test stack file
// --------------------------------------------
const

  OutFileName = 'test.pic' ;
  NumStacks = 20 ;
  NumFramesPerStack = 21 ;
  FrameWidth = 300 ;
  FrameHeight = 200 ;
  PixelDepth = 8 ;
  ComponentsPerPixel = 1 ;
  XRadius = 4.0 ;
  YRadius = 4.0 ;
  ZRadius = 4.0 ;
  XRadiusSquared = XRadius*XRadius ;
  YRadiusSquared = YRadius*YRadius ;
  ZRadiusSquared = ZRadius*ZRadius ;
var
   iStack : Integer ;
   PFrameBuf : Pointer ;        // Image frame buffer pointer
   i : Integer ;
   x,y,z : Integer ;
   x0,y0,z0 : Integer ;
   Xmicrons,Ymicrons,Zmicrons : Single  ;
   r : Single ;
   NumPixels : Integer ;
   VoxelVolume : Single ;
   NumFramesCreated : Integer ;
begin

    PFrameBuf := Nil ;

    // Create new file
    MergeFile.CreateFile( OutFileName,
                          FrameWidth,
                          FrameHeight,
                          PixelDepth,
                          ComponentsPerPixel,
                          False ) ;

    // Set file calibration factors
    MergeFile.XResolution := 0.25 ;
    MergeFile.YResolution := 0.25 ;
    MergeFile.ZResolution := 1.0 ;
    MergeFile.TResolution := 1.0 ;

    VoxelVolume := MergeFile.XResolution*
                   MergeFile.YResolution*
                   MergeFile.ZResolution ;

    // Allocate frame buffer
    GetMem( PFrameBuf,MergeFile.NumBytesPerFrame ) ;

    // Import frames from this file
    x0 := FrameWidth div 2 ;
    y0 := FrameHeight div 2 ;
    z0 := NumStacks div 2 ;
    NumFramesCreated := 0 ;
    for iStack := 1 to NumStacks do begin

        NumPixels := 0 ;
        for z := 0 to NumFramesPerStack-1 do begin

           // Clear frame
           for i := 0 to MergeFile.NumPixelsPerFrame-1 do
               PByteArray(PFrameBuf)^[i] := 0 ;

           // Set object

           for x := 0 to MergeFile.FrameWidth-1 do
               for y := 0 to MergeFile.FrameHeight-1 do begin
                   Xmicrons := (x-x0)*MergeFile.XResolution ;
                   Ymicrons := (y-y0)*MergeFile.YResolution ;
                   Zmicrons := (z-z0)*MergeFile.ZResolution ;
                   r := ((Xmicrons*Xmicrons)/XRadiusSquared) +
                        ((Ymicrons*Ymicrons)/YRadiusSquared) +
                        ((Zmicrons*Zmicrons)/ZRadiusSquared) ;
                   if r <= 1.0 then begin
                      i := y*FrameWidth + x ;
                      PByteArray(PFrameBuf)^[i] := 130 ;
                      Inc(NumPixels)
                      end ;
                   end ;

           Inc(NumFramesCreated) ;
           MergeFile.SaveFrame( NumFramesCreated, PFrameBuf ) ;

           end ;

        MainFrm.StatusBar.SimpleText := format(
        'TEST FILE: Creating %.3d/%.3d frames in %s (Vol=%4g um)',
        [NumFramesCreated,
         NumFramesPerStack*NumStacks,
         ExtractFileName(OutFileName),
         NumPixels*VoxelVolume
         ]) ;

        x0 := x0 + Round(RandG( 0.0, XRadius*2 )) ;
        y0 := y0 + Round(RandG( 0.0, XRadius*2 )) ;
        z0 := z0 + Round(RandG( 0.0, XRadius )) ;

        end ;

    // Close output file
    MergeFile.CloseFile ;

    if PFrameBuf <> Nil then FreeMem( PFrameBuf ) ;

    // Display file
    CreateNewViewFrm( OutFileName ) ;

    end ;





procedure TMainFrm.mnTestFileClick(Sender: TObject);
begin
     TestStack ;
     end;

procedure TMainFrm.mnOpenTrackFileClick(Sender: TObject);
// ------------------------------
// Open object tracking data file
// ------------------------------
var
    ResultsFrmNum : Integer ;
begin
     OpenDialog.options := [ofPathMustExist] ;
     OpenDialog.DefaultExt := TrackFileExtension ;

     if MainFrm.DataDirectory <> '' then OpenDialog.InitialDir := MainFrm.DataDirectory ;

     OpenDialog.Filter :=
     'Results Files (*.res)|*.res|' ;
     OpenDialog.Title := 'Open Object/Track Results Table ' ;

     if not OpenDialog.Execute then Exit ;

     ResultsFrmNum := MainFrm.CreateNewResultsFrm( OpenDialog.FileName ) ;
     ResultsFrms[ResultsFrmNum].ReadFromFile( OpenDialog.FileName ) ;

     end;

procedure TMainFrm.FormClose(Sender: TObject; var Action: TCloseAction);
// -----------------
// Close application
// -----------------
var
    i : Integer ;
begin
     // Close all forms
     for i := 0 to MDIChildCount-1 do MDIChildren[i].Close ;
     end;

procedure TMainFrm.mnMergeByAlphabeticClick(Sender: TObject);
// ---------------------------------------
// Merge files (sorted in alphabetic order)
// ---------------------------------------
begin
    MergeFiles( MergeAlphabetic ) ;
    end;

procedure TMainFrm.mnSortedNumericallyClick(Sender: TObject);
// ---------------------------------------
// Merge files (sorted by trailing index number)
// ---------------------------------------
begin
    MergeFilesfrm.Left := Left + 10 ;
    MergeFilesfrm.Top := Top + 50 ;
    MergeFilesfrm.ShowModal ;
    end;


procedure TMainFrm.mnSeparateRGBClick(Sender: TObject);
// ---------------------------------------
// Open Separate RGB components dialog box
// ---------------------------------------
begin
     RGBSeparateFrm.Left := Left + 10 ;
     RGBSeparateFrm.Top := Top + 50 ;
     RGBSeparateFrm.ShowModal ;
     end;

end.
