unit ViewUnit;
// -------------------------------------
// PICViewer - PIC images display module
// -------------------------------------
// 25.3.03
// 28.3.03 Regions of interest can now be saved/loaded from file
// 11.4.03 Green, red and blue scale display LUT mapping added
//         Now exits if opened with empty PIC file
// 21.4.04 BIORAD & TIFF file code now within components
// 22.4.03 Curve fitting added to X-Y plot
// 24.4.03 Graphs and image can now be copied to clipboard
// .Z file defining Z axis dimension is now loaded with image file
// 29.5.03 ROIs can now be dragged from corners as well as edges
// 30.6.03 Default rectangular ROI now created
// 29.10.03 400% display magnification added
// 3.11.03  Max Contrast now uses ROIs and can be set over all frame
//          100% point of % plot now set using x=0 cursor
// 09.03.04 3D/4D display facility added
// 11.03.04 Movie forward/reverse playback added
// 29.03.04 Zero of false colour changed to black
// 26.04.04 ROI plot transferred to GraphFrm
// 11.05.04 Spurious histogram peak at 128 with 8 bit images fixed
// 31.05.04 Image resolutions now represented as form properties
// 25.06.09 Display area can now be scrolled in X and Y
//          Magnification now from 25% - 600%

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, RangeEdit, ExtCtrls, ComCtrls, maths,
  XYPlotDisplay, ClipBrd, ImageFile, ValidatedEdit, CurveFitter, OleCtrls,
  SHDocVw, Printers ;

const
    MaxFrames = 30000 ;
    MaxComponents = 3 ;
    MinPaletteColor = 10 ;
    MaxPaletteColor = 255 ;
    MinRGBColor = 0 ;
    MaxRGBColor = 255 ;
    LUTMax = $FFFF ;
    MaxROIs = 9 ;
    ROIFileExtension = 'ROI' ;
    ZFileExtension = '.Z' ;

type
 TSmallIntArray = Array[0..4095*4095] of SmallInt ;
 PSmallIntArray = ^TSmallIntArray ;
 TIntArray = Array[0..4095*4095] of Integer ;
 PIntArray = ^TIntArray ;
 TPaletteType = (palGrey,palGreen,palRed,palBlue,palFalseColor) ;
 TImageFileType = (PICFile,TIFFFile) ;

    TMoveMode = (mvNone,mvLeftEdge,mvRightEdge,mvTopEdge,mvBottomEdge,
                 mvTopLeft,mvBottomLeft,mvTopRight,mvBottomRight,mvAll) ;


  // Region of interest definition record
  TROIShape = (CrossHairsROI,RectangleROI,EllipseROI,LineROI) ;
  TROI = record
         InUse : Boolean ;
         Shape : TROIShape ;
         TopLeft : TPoint ;
         BottomRight : TPoint ;
         Centre : TPoint ;
         Width : Integer ;
         Height : Integer ;
         ZoomFactor : Single ;
         end ;

  TRunElement = record
        x : Word ;
        y : Word ;
        z : Word ;
        Count : Word ;
        ObjectID : Word ;
        end ;


  TViewFrm = class(TForm)
    ControlGrp: TGroupBox;
    DisplayGrp: TGroupBox;
    ContrastGrp: TGroupBox;
    bOptimiseContrast: TButton;
    bFullScale: TButton;
    edDisplayIntensityRange: TRangeEdit;
    cbDisplayZoom: TComboBox;
    FrameGrp: TGroupBox;
    Timer: TTimer;
    Page: TPageControl;
    ImagePage: TTabSheet;
    HistogramPage: TTabSheet;
    Hist: TXYPlotDisplay;
    lbHistCursor: TLabel;
    lbImageCursor: TLabel;
    ROIGrp: TGroupBox;
    GroupBox5: TGroupBox;
    bAddROI: TButton;
    cbROIType: TComboBox;
    GroupBox6: TGroupBox;
    bDeleteROI: TButton;
    cbDeleteROI: TComboBox;
    bDeleteAllROI: TButton;
    Image: TImage;
    StatisticsGrp: TGroupBox;
    meStatistics: TMemo;
    SaveDialog: TSaveDialog;
    OpenDialog: TOpenDialog;
    bLoadROis: TButton;
    bSaveROIs: TButton;
    Label1: TLabel;
    ImageFile: TImageFile;
    SaveFile: TImageFile;
    PropertiesPage: TTabSheet;
    meProperties: TMemo;
    PixelDimensionsGrp: TGroupBox;
    Label5: TLabel;
    Label7: TLabel;
    edXResolution: TValidatedEdit;
    CalBarGrp: TGroupBox;
    Label8: TLabel;
    edCalBarWidth: TValidatedEdit;
    ckDisplayCalBar: TCheckBox;
    Fit: TCurveFitter;
    HistGrp: TGroupBox;
    GroupBox8: TGroupBox;
    cbHistROI: TComboBox;
    cbContrastROI: TComboBox;
    Label9: TLabel;
    ckOptimiseForAllFrames: TCheckBox;
    cbPalette: TComboBox;
    SectionPanel: TPanel;
    edYResolution: TValidatedEdit;
    Label12: TLabel;
    Label13: TLabel;
    edZResolution: TValidatedEdit;
    Label6: TLabel;
    edTResolution: TValidatedEdit;
    lbFrameInfo: TLabel;
    PlayButtonsGrp: TGroupBox;
    bGoToStart: TButton;
    bGoToEnd: TButton;
    bBackwards: TButton;
    bForwards: TButton;
    bStop: TButton;
    edNumSectionsPerStack: TValidatedEdit;
    lbNumSectionsPerStack: TLabel;
    sbStackNum: TScrollBar;
    edStackNum: TRangeEdit;
    edSectionNum: TRangeEdit;
    Label10: TLabel;
    sbSectionNum: TScrollBar;
    lbFrame: TLabel;
    bSetHistAxes: TButton;
    Label2: TLabel;
    cbPixelUnits: TComboBox;
    sbXScroll: TScrollBar;
    sbYScroll: TScrollBar;
    procedure FormShow(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure sbSectionNumChange(Sender: TObject);

    procedure bFullScaleClick(Sender: TObject);
    procedure bOptimiseContrastClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure cbDisplayZoomChange(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure edDisplayIntensityRangeKeyPress(Sender: TObject;
      var Key: Char);
    procedure ImageMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure PageChange(Sender: TObject);
    procedure HistCursorChange(Sender: TObject);
    procedure ImageMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ImageMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure bAddROIClick(Sender: TObject);
    procedure bDeleteROIClick(Sender: TObject);
    procedure bDeleteAllROIClick(Sender: TObject);
    procedure cbROITypeChange(Sender: TObject);
    procedure bLoadROisClick(Sender: TObject);
    procedure bSaveROIsClick(Sender: TObject);
    procedure cbPaletteChange(Sender: TObject);
    procedure rbHistWholeImageClick(Sender: TObject);
    procedure rbROIClick(Sender: TObject);
    procedure cbHistROIClick(Sender: TObject);
    procedure edXResolutionKeyPress(Sender: TObject; var Key: Char);
    procedure bSetHistAxesClick(Sender: TObject);
    procedure rb3DClick(Sender: TObject);
    procedure rb4DClick(Sender: TObject);
    procedure edSectionNumKeyPress(Sender: TObject; var Key: Char);
    procedure edStackNumKeyPress(Sender: TObject; var Key: Char);
    procedure edNumSectionsPerStackKeyPress(Sender: TObject;
      var Key: Char);
    procedure sbStackNumChange(Sender: TObject);
    procedure edYResolutionKeyPress(Sender: TObject; var Key: Char);
    procedure edZResolutionKeyPress(Sender: TObject; var Key: Char);
    procedure bForwardsClick(Sender: TObject);
    procedure bBackwardsClick(Sender: TObject);
    procedure bStopClick(Sender: TObject);
    procedure bGoToStartClick(Sender: TObject);
    procedure bGoToEndClick(Sender: TObject);
    procedure ImageDblClick(Sender: TObject);
    procedure edTResolutionKeyPress(Sender: TObject; var Key: Char);
    procedure cbPixelUnitsChange(Sender: TObject);
    procedure sbXScrollChange(Sender: TObject);
    procedure sbYScrollChange(Sender: TObject);

  private
    { Private declarations }
    NumBytesPerPixel : Integer ;      // No. of bytes per pixel
    NumPixelsPerFrame : Integer ;     // No. of pixels per frame
    NumComponentsPerFrame : Integer ; // No. of colour components per frame
    NumBytesPerFrame : Integer ;      // No. of bytes per frame
    DisplayZoom : Single ;        // Display magnification factor

    PFrameBuf : Pointer ;    // Pointer to frame storage buffer
    PDisplayBuf : Pointer ;  // Pointer to display storage buffer
    PWorkBuf : Pointer ;
    LUT : Array[0..LUTMax] of Integer ; // Display look up table

    PaletteType : Integer ;  // Display colour palette
    OptimiseContrast : Boolean ; // Image contrast optimisation request
    MouseDown : Boolean ;    // Mouse button is down
    ImagePropertiesChanged : Boolean ;
    HistCursorNum : Integer ;
    HistCursorX : Single ;
    InitialisePlotCursors : Boolean ;
    PlotCursorNum : Integer ;
    PlotCursorNumPos : Single ;
    PlotCursor0 : Integer ;
    PlotCursor0Pos : Single ;
    PlotCursor1 : Integer ;
    PlotCursor1Pos : Single ;
    PlotCursor2 : Integer ;
    PlotCursor2Pos : Single ;
    FittedPlotNum : Integer ;

    DefaultROIFile : String ;
    ROIColor : TColor ;

    MoveMode : TMoveMode ;
    MouseXPosition : Integer ;       // Latest X position at mouse event
    MouseYPosition : Integer ;       // Latest Y position at mouse event


    // Clipboard image data
    ClipboardImageFormat : Word ;
    ClipboardImageData: THandle ;
    ClipboardPalette : HPalette ;

    procedure Set4DControls ;

    procedure SetImageSize ;

    procedure UpdateImage(
             NewFrame : Integer )  ;
                                            
    procedure DisplayImageReduce(
              PDisplayBuf : Pointer ;
              BitMap : TBitMap ;
              Image : TImage ) ;

    procedure DisplayImageMag(
              PDisplayBuf : Pointer ;
              BitMap : TBitMap ;
              Image : TImage ) ;

    function LoadZFile : Boolean ;

    procedure AdjustROI(
              X : Integer ;
              Y : Integer ) ;

    procedure UpdateROIs ;

    procedure DrawROI(
              ROI : TROI ;
              ROINum : Integer ;
              Canvas : TCanvas
              ) ;

    procedure ScaleROI(
              const SrcROI : TROI ;
              var DestROI : TROI ) ;

    procedure UnscaleROI(
              const SrcROI : TROI ;
              var DestROI : TROI  ) ;


    procedure UpdateHistogram ;
    procedure UpdateHistogramROIList ;

    function GetXResolution : Single ;
    procedure SetXResolution( Value : Single ) ;
    function GetYResolution : Single ;
    procedure SetYResolution( Value : Single ) ;
    function GetZResolution : Single ;
    procedure SetZResolution( Value : Single ) ;
    function GetTResolution : Single ;
    procedure SetTResolution( Value : Single ) ;


  public
    { Public declarations }
    Index : Integer ;
    FileName : String ;      // Name of PIC file on display
    FileType : TImageFileType ;
    Display4DMode : Boolean ;
    ROIs : Array[0..MaxROIs] of TROI ;      // Defined regions of interest list
    ROIsUnscaled : Array[0..MaxROIs] of TROI ;  // Defined regions of interest list

    CurrentFrame : Integer ;        // Currently displayed frame
    NumFrames : Integer ;           // No. of frames in file
    CurrentSection : Integer ;
    NumSectionsPerStack : Integer ; // No. of sections per stack
    CurrentStack : Integer ;
    NumStacks : Integer ;           // No. of multi-section stacks

    GreyMax : Integer ;      // Grey scale maximum
    GreyLo : Integer ;       // Lower limit of contrast range
    GreyHi : Integer ;       // Upper limit of contrast range

    //XResolution : Single ;   // Horizontal pixel size
    //YResolution : Single ;   // Vertical pixel size
    //ZResolution : Single ;   // Z stack spacing
    //TResolution : Single ;   // Inter-frame time interval (s)
    PixelUnits : string ;       // Pixel width units

    IntensityMean : Single ;
    IntensitySD : Single ;
    IntensityMin : Single ;
    IntensityMax : Single ;
    FrameWidth : Integer ;
    FrameHeight : Integer ;
    PixelDepth : Integer ;
    NumComponentsPerPixel : Integer ;
    ByteImage : Boolean ;
    CopyDataAvailable : Boolean ;
    CopyImageAvailable : Boolean ;

    ZAxisDefined : Boolean ;         // Z axis values defined flag
    ZUnits : String ;                            // Z axis units
    ZLabel : String ;                           // Z axis label
    ZValues : Array[0..MaxFrames-1] of Single ; // Z axis values

    BitMap : TBitMap ;               // Bitmap of displayed image bitmap

    // Latest pixel selected by double-clicking
    DblClickX : Integer ;
    DblClickY : Integer ;
    DblClickPixelIntensity : Integer ;
    DblClickStackNum : Integer ;

    procedure SetBMapPalette( BitMap : TBitMap ; PaletteType : TPaletteType ) ;
    procedure UpdateLUT ;
    procedure ImageStatistics ;
    procedure SetFrame( FrameNum : Integer ) ;
    procedure SetStackNum( StackNum : Integer ) ;
    procedure SetSectionNum( SectionNum : Integer ) ;
    procedure SetNumSectionsPerStack( NumSections : Integer ) ;
    procedure SetPalette( iPalette : TPaletteType ) ;
    procedure LoadROIsFromFile( FileName : String ) ;
    procedure SaveROIsToFile( FileName : String ) ;
    procedure SaveToFile(
              DestFileName : String ;
              SavePixelDepth : Integer )  ;
    procedure CopyDataToClipboard ;
    procedure CopyImageToClipboard ;
    procedure Print ;

    function LoadFrame( FrameNum : Integer ; PBuf : Pointer ) : Boolean ;
    function SaveFrame( FrameNum : Integer ; PBuf : Pointer ) : Boolean ;

    function GetROIPixelRuns(
             iROI : Integer ;                 // Region of interest #
             var Runs : Array of TRunElement ;// Pixel runs
             var NumRuns : Integer            // No. pixel runs
             ) : Single ;
    Property XResolution : Single read GetXResolution write SetXResolution ;
    Property YResolution : Single read GetYResolution write SetYResolution ;
    Property ZResolution : Single read GetZResolution write SetZResolution ;
    Property TResolution : Single read GetTResolution write SetTResolution ;
  end;

  // Corrected system function call template
function GetSystemPaletteEntries(
         DC : HDC ;
         StartIndex : Cardinal ;
         NumEntries : Cardinal ;
         PaletteEntries : Pointer ) : Cardinal ; stdcall ;

var
  ViewFrm: TViewFrm;

implementation

uses PicViewMain, shared, Setfitpa, Printgra, Setaxes, math ;

{$R *.dfm}


    
function GetSystemPaletteEntries ; external gdi32 name 'GetSystemPaletteEntries' ;


procedure TViewFrm.FormShow(Sender: TObject);
// ------------------------------------
// Initialise form when first displayed
// ------------------------------------
var
     i : Integer ;
     OK : Boolean ;
     ROIAvailable : Boolean ;   // ROIs available flag
begin

     // Make sure timer is not running
     Timer.Enabled := False ;

     // Display magnification factor
     cbDisplayZoom.Clear ;
     cbDisplayZoom.Items.AddObject( '  25% ',TObject(25) ) ;
     cbDisplayZoom.Items.AddObject( '  50% ',TObject(50) ) ;
     cbDisplayZoom.Items.AddObject( ' 100% ',TObject(100) ) ;
     cbDisplayZoom.Items.AddObject( ' 200% ',TObject(200) ) ;
     cbDisplayZoom.Items.AddObject( ' 300% ',TObject(300) ) ;
     cbDisplayZoom.Items.AddObject( ' 400% ',TObject(400) ) ;
     cbDisplayZoom.Items.AddObject( ' 500% ',TObject(500) ) ;
     cbDisplayZoom.Items.AddObject( ' 600% ',TObject(600) ) ;
     cbDisplayZoom.ItemIndex := 2 ;
     DisplayZoom := 0.01*Integer(cbDisplayZoom.Items.Objects[cbDisplayZoom.ItemIndex]) ;

     // Intensity display palette
     cbPalette.Clear ;
     cbPalette.Items.AddObject(' Grey scale', TObject(palGrey)) ;
     cbPalette.Items.AddObject(' False colour', TObject(palFalseColor)) ;
     cbPalette.Items.AddObject(' Red scale', TObject(palRed)) ;
     cbPalette.Items.AddObject(' Green scale', TObject(palGreen)) ;
     cbPalette.Items.AddObject(' Blue scale', TObject(palBlue)) ;
     cbPalette.ItemIndex := 0 ;

     cbPixelUnits.Clear ;
     cbPixelUnits.Items.Add('um') ;
     cbPixelUnits.Items.Add('cm') ;
     cbPixelUnits.Items.Add('inches') ;
     cbPixelUnits.ItemIndex := 0 ;

    // Open PIC file
    FileName := MainFrm.NewFileName ;
    Caption := FileName ;

    if (LowerCase(ExtractFileExt(FileName)) = '.tif') or
       (LowerCase(ExtractFileExt(FileName)) = '.stk') then FileType := TIFFFile
                                                      else FileType := PICFile ;

    OK := ImageFile.OpenFile( FileName ) ;
    NumFrames := ImageFile.NumFrames ;
    FrameWidth := ImageFile.FrameWidth ;
    FrameHeight := ImageFile.FrameHeight ;

    PixelDepth := ImageFile.PixelDepth ;
    NumComponentsPerPixel := ImageFile.ComponentsPerPixel ;
    if PixelDepth <= 8 then begin
       ByteImage := True ;
       NumBytesPerPixel := 1 ;
       end
    else begin
       ByteImage := False ;
       NumBytesPerPixel := 2 ;
       end ;

    NumPixelsPerFrame := FrameWidth*FrameHeight ;
    NumComponentsPerFrame := NumPixelsPerFrame*NumComponentsPerPixel ;
    NumBytesPerFrame := NumPixelsPerFrame*NumComponentsPerPixel*NumBytesPerPixel ;

    // Update image resolutions
    XResolution := ImageFile.XResolution ;
    edXResolution.Value := XResolution ;
    YResolution := ImageFile.XResolution ;
    edYResolution.Value := YResolution ;
    ZResolution := ImageFile.ZResolution ;
    edZResolution.Value := ZResolution ;
    PixelUnits := ImageFile.ResolutionUnit ;
    if cbPixelUnits.Items.IndexOf(PixelUnits) >= 0 then begin
       cbPixelUnits.ItemIndex := cbPixelUnits.Items.IndexOf(PixelUnits) ;
       end
    else begin
       cbPixelUnits.Items.Add(PixelUnits) ;
       cbPixelUnits.ItemIndex :=  cbPixelUnits.Items.Count-1 ;
       end ;

    edXResolution.Units := PixelUnits ;
    edYResolution.Units := PixelUnits ;
    edZResolution.Units := PixelUnits ;

    TResolution := ImageFile.TResolution ;
    edTResolution.Value := TResolution ;
    ImagePropertiesChanged := False ;

    edCalBarWidth.Value := 0.2*FrameWidth*XResolution ;
    edCalBarWidth.Units := PixelUnits ;
    ckDisplayCalBar.Checked := True ;

    DblClickX := 0 ;
    DblClickY := 0 ;
    DblClickPixelIntensity := 0 ;
    DblClickStackNum := 0 ;

    // Exit if file cannot be opened
    if not OK then begin
       MainFrm.StatusBar.SimpleText := 'Unable to open : ' + MainFrm.NewFileName ;
       Close ;
       Exit ;
       end ;



    // Exit if file ccontains no images
    if (NumFrames <= 0) then begin
       MainFrm.StatusBar.SimpleText :=
       'Unable to open : ' + MainFrm.NewFileName + ' (no images in file)';
       Close ;
       Exit ;
       end ;

     // Intensity display palette
     cbPalette.Clear ;
     if NumComponentsPerPixel = 1 then begin
        cbPalette.Items.AddObject(' Grey scale', TObject(palGrey)) ;
        cbPalette.Items.AddObject(' False colour', TObject(palFalseColor)) ;
        cbPalette.Items.AddObject(' Red scale', TObject(palRed)) ;
        cbPalette.Items.AddObject(' Green scale', TObject(palGreen)) ;
        cbPalette.Items.AddObject(' Blue scale', TObject(palBlue)) ;
        cbPalette.ItemIndex := 0 ;
        end ;

     // Create and set size of bitmap and image control
     BitMap := TBitMap.Create ;
     SetImageSize ;

     // Set bit map pixel format
     if NumComponentsPerPixel > 1 then begin
        // RGB (24 bit) bitmaps
        BitMap.PixelFormat := pf24bit ;
        end
     else begin
        // monochrome (8 bit) bitmaps
        BitMap.PixelFormat := pf8bit ;
        SetBMapPalette( BitMap,
                        TPaletteType(cbPalette.Items.Objects[cbPalette.ItemIndex])) ;
        end ;

     ImagePage.ControlStyle := ImagePage.ControlStyle + [csOpaque] ;
     Image.ControlStyle := Image.ControlStyle + [csOpaque] ;
     Image.Width := BitMap.Width ;
     Image.Height := BitMap.Height ;
     lbImageCursor.Left := Image.Left ;
     lbImageCursor.Top := Image.Top + Image.Height + 2 ;
     lbImageCursor.Visible := False ;
     lbFrameInfo.Visible := False ;

     ClientWidth := Page.Left + Image.Left*2 + Image.Width + 5 ;
     ClientHeight := Page.Top + Image.Top*2 + Image.Height
                     + lbImageCursor.Height + 50 ;
     Page.Width := ClientWidth - Page.Left - 5 ;
     Page.Height := ClientHeight - Page.Top - 5  ;

     GetMem( PFrameBuf, NumBytesPerFrame ) ;
     GetMem( PDisplayBuf, NumComponentsPerFrame*4 ) ;
     GetMem( PWorkBuf, NumComponentsPerFrame*4 ) ;

     // Initialise look-up table to direct mapping
     for i := 0 to High(LUT) do LUT[i] := i ;

     // Grey scale maximum
     GreyMax := 1 ;
     for i := 1 to PixelDepth do GreyMax := GreyMax*2 ;
     GreyMax := GreyMax - 1 ;
     edDisplayIntensityRange.HiLimit := GreyMax ;

     sbSectionNum.Position := 1 ;
     sbStackNum.Position := 1 ;

     // Load associated Z dimension file (if it exists)
     LoadZFile ;

     // Load current set of ROIs
     DefaultROIFile := MainFrm.DefaultDataDirectory + 'default.roi' ;
     if FileExists( DefaultROIFile ) then LoadROIsFromFile( DefaultROIFile ) ;

     // Region of interest overlay colours
     ROIColor := clWhite ;

     // Region of interest type list
     cbROIType.Clear ;
     cbROIType.Items.AddObject( 'Cross-hairs', TObject(CrossHairsROI)) ;
     cbROIType.Items.AddObject( 'Line', TObject(LineROI)) ;
     cbROIType.Items.AddObject( 'Rectangle', TObject(RectangleROI)) ;
     cbROIType.Items.AddObject( 'Ellipse', TObject(EllipseROI)) ;
     cbROIType.ItemIndex := 0 ;

     // Enable live ROI
     ROIs[0].InUse := True ;
     ROIs[0].Shape := TROIShape(
                      cbROIType.Items.Objects[cbROIType.ItemIndex] ) ;

     // Create default rectangular ROI in middle of image if none exist
     ROIAvailable := False ;
     for i := 1 to High(ROIs) do if ROIsUnscaled[i].InUse then  ROIAvailable := True ;
     if {not ROIAvailable} false then begin
        ROIsUnscaled[1].Width :=  Image.Width div 2 ;
        ROIsUnscaled[1].Height := Image.Width div 2 ;
        ROIsUnscaled[1].Centre.X := Image.Width div 2 ;
        ROIsUnscaled[1].Centre.Y := Image.Height div 2 ;
        ROIsUnscaled[1].TopLeft.X := ROIsUnscaled[1].Centre.X - ROIsUnscaled[1].Width div 2 ;
        ROIsUnscaled[1].BottomRight.X := ROIsUnscaled[1].TopLeft.X + ROIsUnscaled[1].Width - 1 ;
        ROIsUnscaled[1].TopLeft.Y := ROIsUnscaled[1].Centre.Y - ROIsUnscaled[1].Height div 2 ;
        ROIsUnscaled[1].BottomRight.Y := ROIsUnscaled[1].TopLeft.Y + ROIsUnscaled[1].Height - 1 ;
        ROIsUnscaled[1].InUse := True ;
        ROIsUnscaled[1].Shape := RectangleROI ;
        end ;

     // Load internal ROI records from master records
     // scaled by display zoom factor
     for i := 0 to High(ROIs) do ScaleROI(ROIsUnscaled[i],ROIs[i]) ;

     // Set all regions of interest deletion list
     cbDeleteROI.Clear ;
     for i := 1 to High(ROIs) do if ROIs[i].InUse then
        cbDeleteROI.Items.Add(format('%d',[i])) ;

     if cbDeleteROI.Items.Count > 0 then bDeleteROI.Enabled := True
                                    else cbDeleteROI.Enabled := False ;
     bDeleteAllROI.Enabled := bDeleteROI.Enabled ;

     UpdateHistogramROIList ;

     // Initial position/size of live region of interest
     ROIs[0].Width :=  Image.Width div 12 ;
     ROIs[0].Height := Image.Width div 12 ;
     ROIs[0].Centre.X := Image.Width div 2 ;
     ROIs[0].Centre.Y := Image.Height div 2 ;

     ROIs[0].TopLeft.X := ROIs[0].Centre.X - ROIs[0].Width div 2 ;
     ROIs[0].BottomRight.X := ROIs[0].TopLeft.X + ROIs[0].Width - 1 ;
     ROIs[0].TopLeft.Y := ROIs[0].Centre.Y - ROIs[0].Height div 2 ;
     ROIs[0].BottomRight.Y := ROIs[0].TopLeft.Y + ROIs[0].Height - 1 ;
     UnscaleROI( ROIs[0],ROIsUnscaled[0] ) ;

     // Open results file (if one exists)
     //ResultsFile :=  ChangeFileExt(FileName, '.res') ;
     //if FileExists(ResultsFile) then begin
     //   ResultsFrmNum := MainFrm.CreateNewResultsFrm( 'Results: ' + ResultsFile ) ;
     //   end ;

     // Request image contrast optimisation
     OptimiseContrast := True ;
     CurrentFrame := -1 ;
     MouseDown := False ;
     Page.ActivePage := ImagePage ;

     Resize ;

     Set4DControls ;

     // Start timer (which does actual display of images)
     Timer.Enabled := True ;

     end;

procedure TViewFrm.Set4DControls ;
// --------------------------------------
// Set 2D (frame) / 3D stack display mode
// --------------------------------------
begin

     NumSectionsPerStack := Round(edNumSectionsPerStack.Value) ;
     NumStacks := NumFrames div NumSectionsPerStack ;

     if NumSectionsPerStack > 1 then begin
        // 4D display mode
        SectionPanel.Visible := True ;
        PlayButtonsGrp.Top := SectionPanel.Top +  SectionPanel.Height - 2 ;
        lbFrame.Caption := 'St.' ;
        end
     else begin
        // 3D display mode
        SectionPanel.Visible := False ;
        PlayButtonsGrp.Top := sbStackNum.Top +  sbStackNum.Height ;
        lbFrame.Caption := 'Fr.' ;
        end ;

     edNumSectionsPerStack.Top := PlayButtonsGrp.Top + PlayButtonsGrp.Height + 1;
     lbNumSectionsPerStack.Top := edNumSectionsPerStack.Top ;
     FrameGrp.ClientHeight := lbNumSectionsPerStack.Top + lbNumSectionsPerStack.Height + 5 ;

     DisplayGrp.Top := FrameGrp.Top +  FrameGrp.Height + 1 ;
     ContrastGrp.Top := DisplayGrp.Top +  DisplayGrp.Height + 1 ;
     ROIGrp.Top := ContrastGrp.Top +  ContrastGrp.Height + 1 ;
     StatisticsGrp.Top := ROIGrp.Top +  ROIGrp.Height + 1 ;

     sbSectionNum.Max := NumSectionsPerStack ;
     edSectionNum.HiValue := sbSectionNum.Max ;
     sbStackNum.Max := NumStacks ;
     edStackNum.HiValue := NumStacks ;

     end ;


procedure TViewFrm.UpdateHistogramROIList ;
// ------------------------------------------
// Update contrast range & histogram ROI list
// ------------------------------------------
var
     i : Integer ;
begin

     cbContrastROI.Clear ;
     cbContrastROI.Items.AddObject('Image',TObject(0)) ;
     for i := 1 to High(ROIs) do if ROIs[i].InUse then begin
         if (ROIs[i].Shape = LineROI) or
            (ROIs[i].Shape = RectangleROI) or
            (ROIs[i].Shape = EllipseROI) then
            cbContrastROI.Items.AddObject(format('ROI#%d',[i]),TObject(i)) ;
         end ;
     cbContrastROI.ItemIndex := 0 ;
     cbHistROI.Items.Assign(cbContrastROI.Items);
     cbHistROI.ItemIndex := cbContrastROI.ItemIndex ;
     end ;


procedure TViewFrm.DisplayImageReduce(
          PDisplayBuf : Pointer ; // Pointer to raw image data buffer (IN)
          BitMap : TBitMap ;      // Bit map containing image to be displayed (IN)
          Image : TImage ) ;      // Image to be updated (OUT)
// ----------------------------------------
// Display image with reduced magnification
// ----------------------------------------
var
     i,Xbm,Ybm,Yim,iStep : Integer ;
     XIm : Integer ;
     PScanLine : PByteArray ;
     iCalBar : Integer ;
     iComp,iComp0,iPixValue,iPixStart : Integer ;

begin

    if (PDisplayBuf = Nil) or (BitMap = Nil) then Exit ;

    Ybm := 0 ;
    Yim := sbYScroll.Position ;
    iStep := Round(1.0/DisplayZoom) ;
    while (Ybm < BitMap.Height) and (Yim < FrameHeight) do begin

      // Get scan line array pointer
      PScanLine := BitMap.ScanLine[Ybm] ;

      // Copy line to bitmap
      xBm := 0 ;
      XIm := sbXScroll.Position ;
      i := (Yim*FrameWidth) + XIm ;
      while (Xbm < BitMap.Width) and (XIm < FrameWidth) do begin
            iPixStart := i*NumComponentsPerPixel ;
            for iComp := NumComponentsPerPixel-1 downto 0 do begin
                iPixValue := Max(Min(PIntArray(PDisplayBuf)^[iPixStart+iComp],LUTMax),0) ;
                PScanLine[Xbm*NumComponentsPerPixel+iComp] := LUT[iPixValue] ;
                end ;
          Inc(Xbm) ;
          Xim := Xim + iStep ;
          i := i + iStep ;
          end ;

      Inc(Ybm) ;
      Yim := Yim + iStep

      end ;

    // Add calibration bar
    if ckDisplayCalBar.Checked then begin
       Bitmap.Canvas.Pen.Color := clWhite ;
       Bitmap.Canvas.Pen.Width := 2 ;
       Bitmap.Canvas.Brush.Style := bsClear ;
       Bitmap.Canvas.Font.Color := clWhite ;

       iCalBar := Round((edCalBarWidth.Value*DisplayZoom)/edXResolution.Value) ;
       Bitmap.Canvas.PolyLine( [Point(2,Bitmap.Height-3),
                               Point(MinInt([2+iCalBar,Bitmap.Width-1]),Bitmap.Height-3)]) ;
       Bitmap.Canvas.TextOut( 2,
                              Bitmap.Height - 3 - Bitmap.Canvas.TextHeight('X'),
                              format('%.3g %s',[edCalBarWidth.Value,cbPixelUnits.Text])) ;
       end ;

    Image.Picture.Assign(BitMap) ;

    CopyImageAvailable := True ;

    end ;


procedure TViewFrm.DisplayImageMag(
          PDisplayBuf : Pointer ; // Pointer to raw image data buffer (IN)
          BitMap : TBitMap ;      // Bit map containing image to be displayed (IN)
          Image : TImage ) ;      // Image to be updated (OUT)
// --------------
// Display image
// --------------
var
     i,j,StartOfLine,Xbm,Ybm,Yim : Integer ;
     PScanLine,PScanLine1 : PByteArray ;
     iCalBar : Integer ;
     NumComponentsPerLine,iComp : Integer ;
     iPixValue,iPixStart : Integer ;
begin

    if (PDisplayBuf = Nil) or (BitMap = Nil) then Exit ;

    // Index to start of image line in image buffer
    Ybm := 0 ;
    StartOfLine := ((sbYScroll.Position*FrameWidth) + sbXScroll.Position) ;
    NumComponentsPerLine := FrameWidth*NumComponentsPerPixel ;

    for Yim := sbYScroll.Position to FrameHeight-1 do begin

        // Create line
        PScanLine := BitMap.ScanLine[Ybm] ;
        Xbm := 0 ;
        for i := StartOfLine to StartOfLine + FrameWidth -1 do begin
            // Dublicate pixels
            for j := 1 to Round(DisplayZoom) do begin
                iPixStart := i*NumComponentsPerPixel ;
                for iComp := NumComponentsPerPixel-1 downto 0 do begin
                    iPixValue := Max(Min(PIntArray(PDisplayBuf)^[iPixStart+iComp],LUTMax),0) ;
                    PScanLine[Xbm*NumComponentsPerPixel+iComp] := LUT[iPixValue] ;
                    end ;
                Inc(Xbm) ;
                end ;
            if Xbm >= BitMap.Width then break ;
            end ;

        // Create additional lines
        for i := 1 to Round(DisplayZoom)-1 do begin
            Inc(Ybm) ;
            if Ybm >= Bitmap.Height then break ;
            PScanLine1 := BitMap.ScanLine[Ybm] ;
            for Xbm := 0 to Bitmap.Width*NumComponentsPerPixel-1 do
                PScanLine1[Xbm] := PScanLine[Xbm] ;
            end ;

       StartOfLine := StartOfLine + FrameWidth ;

       Inc(Ybm) ;
       if Ybm >= Bitmap.Height then break ;

       end ;

    // Add calibration bar
    if ckDisplayCalBar.Checked then begin
       Bitmap.Canvas.Pen.Color := clWhite ;
       Bitmap.Canvas.Pen.Width := 2 ;
       Bitmap.Canvas.Brush.Style := bsClear ;
       Bitmap.Canvas.Font.Color := clWhite ;

       iCalBar := Round((edCalBarWidth.Value*DisplayZoom)/edXResolution.Value) ;
       Bitmap.Canvas.PolyLine( [Point(2,Bitmap.Height-3),
                               Point(MinInt([2+iCalBar,Bitmap.Width-1]),Bitmap.Height-3)]) ;
       Bitmap.Canvas.TextOut( 2,
                              Bitmap.Height - 3 - Bitmap.Canvas.TextHeight('X'),
                              format('%.3g %s',[edCalBarWidth.Value,cbPixelUnits.Text])) ;
       end ;

    Image.Picture.Assign(BitMap) ;

    Canvas.Pen.Mode := pmXOR ;
    CopyImageAvailable := True ;

    end ;


procedure TViewFrm.TimerTimer(Sender: TObject);
// -------------------------
// Timer scheduled processes
// -------------------------
var
    NewFrame : Integer ;
begin

     // Play frame forwards
     if not bForwards.Enabled then begin
        if sbStackNum.Position < sbStackNum.Max then
          sbStackNum.Position := sbStackNum.Position + 1
        else bForwards.Enabled := True ;
        end ;

     // Play frame backwards
     if not bBackwards.Enabled then begin
        if sbStackNum.Position > 1 then
           sbStackNum.Position := sbStackNum.Position - 1
        else bBackwards.Enabled := True ;
        end ;

    // Selected frame #
    NewFrame := sbSectionNum.Position + (sbStackNum.Position-1)*NumSectionsPerStack ;
    if CurrentFrame <> NewFrame then UpdateImage( NewFrame ) ;

    end ;


procedure TViewFrm.UpdateImage(
         NewFrame : Integer )  ;
var
    OK : Boolean ;
    i : Integer ;
begin

    CurrentFrame := NewFrame ;
    CurrentStack := sbStackNum.Position ;
    CurrentSection := sbSectionNum.Position ;

    // Load frame
    OK := LoadFrame( CurrentFrame, PDisplayBuf ) ;
    if not OK then Exit ;

    // Optimise LUT range to give best image contrast
    if OptimiseContrast then begin
       // Set histogram ROI to match contrast ROI
       cbHistROI.ItemIndex := cbContrastROI.ItemIndex ;
       UpdateHistogram ;

       if IntensityMin <> IntensityMax then begin
          GreyLo := Round(IntensityMin) ;
          GreyHi := Round(IntensityMax) ;
          end
       else begin
            for i := 0 to NumPixelsPerFrame-1 do begin
                if PIntArray(PDisplayBuf)^[i] > GreyHi then
                   GreyHi := PIntArray(PDisplayBuf)^[i] ;
                if PIntArray(PDisplayBuf)^[i] < GreyLo then
                   GreyLo := PIntArray(PDisplayBuf)^[i] ;
                end ;
            end ;
       edDisplayIntensityRange.HiValue := GreyHi ;
       edDisplayIntensityRange.LoValue := GreyLo ;
       HistCursorX := (GreyLo + GreyHi)*0.5 ;
       UpdateLUT ;
       OptimiseContrast := False ;
       end ;

    // Display frame in appropriate area
    if DisplayZoom < 1.0 then
       DisplayImageReduce( PDisplayBuf,
                           BitMap,
                           Image )
    else
       DisplayImageMag( PDisplayBuf,
                     BitMap,
                     Image ) ;

    // Update regions of interest
    UpdateROIs ;

    // Display calibration bar
    ImageStatistics ;

    lbFrameInfo.Visible := True ;
    lbFrameInfo.Caption := format('  Z= %.4g %s, T= %.4g s',
                           [(sbSectionNum.Position-1)*ZResolution,
                             PixelUnits,
                             (CurrentFrame-1)*TResolution]) ;
    lbFrameInfo.Left := Image.Left + Image.Width - lbFrameInfo.Width ;
    lbFrameInfo.Top := Image.Top + Image.Height + 1 ;

    if Page.ActivePage = HistogramPage then UpdateHistogram ;

    end ;


function TViewFrm.LoadFrame(
          FrameNum : Integer ;          // No. of frame within file (IN)
          PBuf : Pointer ) : Boolean ;  // Pointer to storage buffer (OUT)
// -------------------------------------
// Load frame <FrameNum> from image file
// -------------------------------------
var
     OK : Boolean ;
     i : Integer ;
begin

     // Load frame
     OK := ImageFile.LoadFrame( FrameNum, PFrameBuf ) ;

     if OK then begin
         // Copy frame into display buffer
         if ByteImage then begin
            for i := 0 to NumComponentsPerFrame-1 do
                PIntArray(PBuf)^[i] := PByteArray(PFrameBuf)^[i] ;
            end
         else begin
            for i := 0 to NumComponentsPerFrame-1 do
                PIntArray(PBuf)^[i] := PWordArray(PFrameBuf)^[i] ;
            end ;
         end ;
     Result := OK ;
     end ;


function TViewFrm.SaveFrame(
          FrameNum : Integer ;          // No. of frame within file (IN)
          PBuf : Pointer ) : Boolean ;  // Pointer to storage buffer (OUT)
// -------------------------------------
// Save frame <FrameNum> to image file
// -------------------------------------
var
     i : Integer ;
begin

    // Copy frame into frame buffer
    if ByteImage then begin
       for i := 0 to NumComponentsPerFrame-1 do
           PByteArray(PFrameBuf)^[i] := PIntArray(PBuf)^[i] ;
       end
    else begin
       for i := 0 to NumComponentsPerFrame-1 do
           PWordArray(PFrameBuf)^[i] := PIntArray(PBuf)^[i] ;
       end ;

    // Save frame
    Result := ImageFile.SaveFrame( FrameNum, PFrameBuf ) ;

    end ;

    
function TViewFrm.LoadZFile : Boolean ;
// ----------------------------------------------
// Load text file with Z dimension for each frame
// ----------------------------------------------
var
     ZFileName : String ; // Name of Z dimension file
     ZFile : TextFile ;   // Z file variable
     TextLine : String ;  // Line read from file
     LineNum : Integer ;
     FrameNum : Integer ;
     i : Integer ;
     Done : Boolean ;
begin

     ZAxisDefined := False ;

     // Name of Z data file
     ZFileName := ChangeFileExt( FileName, ZFileExtension ) ;
     if not FileExists(ZFileName) then begin
        Result := False ;
        Exit ;
        end ;

     // Open file
     AssignFile( ZFile, ZFileName ) ;
     Reset( ZFile ) ;

     // Clear Z values array
     for i := 0 to NumFrames-1 do ZValues[i] := 0.0 ;

     // Get Z axis values from file
     Done := False ;
     FrameNum := 0 ;
     LineNum := 0 ;
     While not Done do begin

         // Read text line from file
         Readln( ZFile, TextLine ) ;

         // Process line
         Inc(LineNum) ;
         if LineNum = 1 then ZLabel := TextLine
         else if LineNum = 2 then ZUnits := TextLine
         else begin
              ZValues[FrameNum] := ExtractFloat(TextLine,0.0) ;
              Inc(FrameNum) ;
              ZAxisDefined := True ;
              end ;

         if EOF(ZFile) or (FrameNum >= NumFrames) then Done := True ;
         end ;

     CloseFile(ZFile) ;

     Result := ZAxisDefined ;
     end ;


procedure TViewFrm.sbSectionNumChange(Sender: TObject);
// -----------------
// Z Section changed
// -----------------
begin
     // Update frame number readout/edit box
     edSectionNum.LoValue := sbSectionNum.Position ;
     end;


procedure TViewFrm.bFullScaleClick(Sender: TObject);
// ------------------------------------------------------
// Set display look-up table to full range of grey levels
// ------------------------------------------------------
begin

     edDisplayIntensityRange.LoValue := 0 ;
     GreyLo := Round(edDisplayIntensityRange.LoValue) ;
     edDisplayIntensityRange.HiValue := GreyMax ;
     GreyHi := Round(edDisplayIntensityRange.HiValue) ;
     UpdateLUT ;
     CurrentFrame := -1 ;

     end;


procedure TViewFrm.bOptimiseContrastClick(Sender: TObject);
// -----------------------------
// Max. Contrast button clicked
// -----------------------------
var
     IMin : Single ;
     IMax : Single ;
     iFrame : Integer ;
begin
      // Request LUT to be updated for best contrast
      OptimiseContrast := True ;
      IMin := GreyMax ;
      IMax := 0 ;
      if ckOptimiseForAllFrames.Checked then begin
         // Optimise contrast for all frames
         cbHistROI.ItemIndex := cbContrastROI.ItemIndex ;
         for iFrame := 1 to NumFrames-1 do begin

            // Load frame
            if not LoadFrame( iFrame, PDisplayBuf ) then Break ;

            // Update histogram (calculates image statistics)
            UpdateHistogram ;

            // Min/max for all frames
            if IMin < IntensityMin then IMin := IntensityMin ;
            if IMax > IntensityMax then IMax := IntensityMax ;

            MainFrm.StatusBar.SimpleText := format(
            'VIEW: Optimising image contrast %d/%d',
            [iFrame,NumFrames] ) ;

            end ;

         MainFrm.StatusBar.SimpleText :=
         'VIEW: Image contrast optimised for all frames in file.' ;
         
         GreyLo := Round(IntensityMin) ;
         GreyHi := Round(IntensityMax) ;
         edDisplayIntensityRange.HiValue := GreyHi ;
         edDisplayIntensityRange.LoValue := GreyLo ;
         UpdateLUT ;
         end
      else OptimiseContrast := True ;

      // Force image re-display
      CurrentFrame := -1 ;

      end;


procedure TViewFrm.SetBMapPalette(
          BitMap : TBitMap ;              // Bitmap to set (IN)
          PaletteType : TPaletteType ) ;  // Type of palette required (IN)
// ------------------------------------------------------
// Set bitmap palette to 8 bit grey or false colour scale
// ------------------------------------------------------
const
    PMin = 10 ;
    PMax = 255 ;
var
  Pal: PLogPalette;
  hpal: HPALETTE;
  i: Integer;
begin

  // Exit if bitmap does not exist
  if BitMap = Nil then Exit ;

  BitMap.PixelFormat := pf8bit ;

  pal := nil;
  GetMem(pal, sizeof(TLogPalette) + sizeof(TPaletteEntry) * 256);

  try

    // Get existing 10 system colours
    GetSystemPaletteEntries( Canvas.Handle, 0, 10, @(Pal^.palPalEntry)) ;

    // Set remaining 246 as shades of grey
    Pal^.palVersion := $300;
    Pal^.palNumEntries := 256;

    case PaletteType of

       // Grey scale
       PalGrey : Begin
           for i := PMin to PMax do begin
               Pal^.palPalEntry[i].peRed := i;
               Pal^.palPalEntry[i].peGreen := i;
               Pal^.palPalEntry[i].peBlue := i;
               end;
           end ;

       // Green scale
       PalGreen : Begin
           for i := PMin to PMax do begin
               Pal^.palPalEntry[i].peRed := 0;
               Pal^.palPalEntry[i].peGreen := i;
               Pal^.palPalEntry[i].peBlue := 0 ;
               end;
           end ;

       // Red scale
       PalRed : Begin
           for i := PMin to PMax do begin
               Pal^.palPalEntry[i].peRed := i;
               Pal^.palPalEntry[i].peGreen := 0;
               Pal^.palPalEntry[i].peBlue := 0 ;
               end;
           end ;

       // Blue scale
       PalBlue : Begin
           for i := PMin to PMax do begin
               Pal^.palPalEntry[i].peRed := 0;
               Pal^.palPalEntry[i].peGreen := 0;
               Pal^.palPalEntry[i].peBlue := i ;
               end;
           end ;

       // False colour
       PalFalseColor : begin
           for i := PMin to PMax do begin
               if i = PMin then begin
                  Pal^.palPalEntry[i].peRed := 0 ;
                  Pal^.palPalEntry[i].peGreen := 0 ;
                  Pal^.palPalEntry[i].peBlue := 0 ;
                  end
               else if i <= 63 then begin
                  Pal^.palPalEntry[i].peRed := 0 ;
                  Pal^.palPalEntry[i].peGreen := 254 - 4*i ;
                  Pal^.palPalEntry[i].peBlue := 255 ;
                  end
               else if i <= 127 then begin
                  Pal^.palPalEntry[i].peRed := 0 ;
                  Pal^.palPalEntry[i].peGreen := 4*i - 254 ;
                  Pal^.palPalEntry[i].peBlue := 510 - 4*i ;
                  end
               else if i <= 191 then begin
                  Pal^.palPalEntry[i].peRed := 4*i - 510 ;
                  Pal^.palPalEntry[i].peGreen := 255 ;
                  Pal^.palPalEntry[i].peBlue := 0 ;
                  end
               else begin
                  Pal^.palPalEntry[i].peRed := 255 ;
                  Pal^.palPalEntry[i].peGreen := 1022 - 4*i ;
                  Pal^.palPalEntry[i].peBlue := 0 ;
                  end ;
               end;
           end ;
       end ;

    hpal := CreatePalette(Pal^);
    if hpal <> 0 then Bitmap.Palette := hpal;

  finally
    FreeMem(Pal);
    end;
  end ;


procedure TViewFrm.UpdateLUT ;
// ----------------------------
// Create display look-up table
// ----------------------------
var
    i,y : Integer ;
    GreyScale : Single ;
    MaxColor : Integer ;
    MinColor : Integer ;
begin

     if NumComponentsPerPixel = 1 then begin
        // Grey scale images using 256 colour palettes
        MaxColor := MaxPaletteColor ;
        MinColor := MinPaletteColor ;
        end
      else begin
        // 24 bit RGB images
        MaxColor := MaxRGBColor ;
        MinColor := MinRGBColor ;
        end ;

     if GreyHi <> GreyLo then GreyScale := MaxColor / (GreyHi - GreyLo)
                         else GreyScale := 1.0 ;

     for i := 0 to GreyMax do begin
         y := MinColor + Round((i-GreyLo)*GreyScale) ;
         if y < MinColor then y := MinColor ;
         if y > MaxColor then y := MaxColor ;
         LUT[i] := y ;
         end ;

     end ;


procedure TViewFrm.AdjustROI(
          X : Integer ;         // Mouse X position (IN)
          Y : Integer ) ;       // Mouse Y position (IN)
// ----------------------------------------------------------
// Update regions of interest and cursor when mouse is moved
// ----------------------------------------------------------
const
    Margin = 2 ;
var
    NewCursor : TCursor ;
    LeftEdge : Integer ;  // Rectangular bounds of current ROI
    RightEdge : Integer ;
    TopEdge : Integer ;
    BottomEdge : Integer ;
begin

     if (not MouseDown) then begin

         // Not in move mode - determine whether mouse is over ROI

         NewCursor := Image.Cursor ;

         LeftEdge := MinInt([ROIs[0].TopLeft.X,ROIs[0].BottomRight.X]) ;
         RightEdge := MaxInt([ROIs[0].TopLeft.X,ROIs[0].BottomRight.X]) ;
         TopEdge := MinInt([ROIs[0].TopLeft.Y,ROIs[0].BottomRight.y]) ;
         BottomEdge := MaxInt([ROIs[0].TopLeft.Y,ROIs[0].BottomRight.y]) ;

         if (TopEdge <= Y) and (Y <= BottomEdge) and
            (LeftEdge <= X) and (X <= RightEdge) then begin

            if ROIs[0].Shape = LineROI then begin
               // Line ROI type
               if (Abs(X-ROIs[0].TopLeft.X) <= Margin) and
                  (Abs(Y-ROIs[0].TopLeft.Y) <= Margin) then begin
                  // Top/left point
                  NewCursor := crSizeAll ;
                  MoveMode := mvTopLeft ;
                  end
               else if (Abs(X-ROIs[0].BottomRight.X) <= Margin) and
                       (Abs(Y-ROIs[0].BottomRight.Y) <= Margin) then begin
                       // Bottom-right point
                       NewCursor := crSizeAll ;
                       MoveMode := mvBottomRight ;
                       end ;
               end

            else begin
               // Other ROI types
               if (Abs(X-ROIs[0].TopLeft.X) <= Margin) then begin
                  if Abs(Y-ROIs[0].TopLeft.Y) <= Margin then begin
                     NewCursor := crSizeNWSE ;
                     MoveMode := mvTopLeft ;
                     end
                  else if Abs(Y-ROIs[0].BottomRight.Y) <= Margin then begin
                     NewCursor := crSizeNESW ;
                     MoveMode := mvBottomLeft ;
                     end
                  else begin
                     NewCursor := crSizeWE ;
                     MoveMode := mvLeftEdge ;
                     end ;
                  end
               else if Abs(X-ROIs[0].BottomRight.X) <= Margin then begin
                  if Abs(Y-ROIs[0].BottomRight.Y) <= Margin then begin
                     NewCursor := crSizeNWSE ;
                     MoveMode := mvBottomRight ;
                     end
                  else if Abs(Y-ROIs[0].TopLeft.Y) <= Margin then begin
                     NewCursor := crSizeNESW ;
                     MoveMode := mvTopRight ;
                     end
                  else begin
                     NewCursor := crSizeWE ;
                     MoveMode := mvRightEdge ;
                     end ;
                  end
               else if (Abs(Y-ROIs[0].TopLeft.Y) <= Margin) then begin
                  NewCursor := crSizeNS ;
                  MoveMode := mvTopEdge ;
                  end
               else if Abs(Y-ROIs[0].BottomRight.Y) <= Margin then begin
                  NewCursor := crSizeNS ;
                  MoveMode := mvBottomEdge ;
                  end
               else begin
                  NewCursor := crDrag ;
                  MoveMode := mvAll ;
                  end ;
               end ;

            // Cross-hairs cursor size cannot be changed
            if ROIs[0].Shape = CrossHairsROI then begin
               NewCursor := crDrag ;
               MoveMode := mvAll ;
               end ;

            end
         else begin
            NewCursor := crCross ;
            MoveMode := mvNone ;
            end ;

        // Update image cursors
        Image.Cursor := NewCursor ;

        end
     else if MouseDown then begin

        // Mouse button is down - adjust ROI if in one of the move modes

        case MoveMode of

             mvAll : begin
                ROIs[0].Centre := Point(X,Y) ;
                ROIs[0].TopLeft.X := ROIs[0].Centre.X - ROIs[0].Width div 2 ;
                ROIs[0].TopLeft.Y := ROIs[0].Centre.Y - ROIs[0].Height div 2 ;
                ROIs[0].BottomRight.X := ROIs[0].TopLeft.X + ROIs[0].Width - 1 ;
                ROIs[0].BottomRight.Y := ROIs[0].TopLeft.Y + ROIs[0].Height - 1 ;
                end ;

             mvLeftEdge : ROIs[0].TopLeft.X := X  ;
             mvRightEdge : ROIs[0].BottomRight.X := X  ;
             mvTopEdge : ROIs[0].TopLeft.Y := Y ;
             mvBottomEdge : ROIs[0].BottomRight.Y := Y ;
             mvTopLeft : Begin
               ROIs[0].TopLeft.X := X  ;
               ROIs[0].TopLeft.Y := Y  ;
               end ;
             mvTopRight : Begin
               ROIs[0].BottomRight.X := X  ;
               ROIs[0].TopLeft.Y := Y  ;
               end ;
             mvBottomLeft : Begin
               ROIs[0].TopLeft.X := X  ;
               ROIs[0].BottomRight.Y := Y  ;
               end ;
             mvBottomRight : Begin
               ROIs[0].BottomRight.X := X  ;
               ROIs[0].BottomRight.Y := Y  ;
               end ;

             end ;

        ROIs[0].Width := ROIs[0].BottomRight.X - ROIs[0].TopLeft.X + 1 ;
        ROIs[0].Height := ROIs[0].BottomRight.Y - ROIs[0].TopLeft.Y + 1 ;
        ROIs[0].Centre.X := ROIs[0].TopLeft.X + ROIs[0].Width div 2 ;
        ROIs[0].Centre.Y := ROIs[0].TopLeft.Y + ROIs[0].Height div 2 ;

        UnscaleROI( ROIs[0], ROIsUnscaled[0] ) ;

        end ;

     // Update regions of interest
     UpdateROIs ;

     end ;



procedure TViewFrm.UpdateROIs ;
// ----------------------------------------------
// Update regions of interest on displayed images
// ----------------------------------------------
var
    i : Integer ;
begin

    // Copy image from internal bitmap to image control
    Image.Picture.Assign(BitMap) ;

    // Set pen characteristics
    Image.Canvas.Pen.Color := clWhite ;
    Image.Canvas.Brush.Style := bsClear ;
    Image.Canvas.Font.Color := clWhite ;

    // Display regions of interest
    ROIs[0].InUse := True ;
    for i := 0 to High(ROIs) do if ROIs[i].InUse then  begin
        ScaleROI( ROIsUnscaled[i], ROIs[i] ) ;
        DrawROI(ROIs[i],i,Image.Canvas) ;
        end ;

    // Update main ROI records
    for i := 0 to High(ROIs) do UnscaleROI(ROIs[i],ROIsUnscaled[i]) ;

    end ;


procedure TViewFrm.DrawROI(
          ROI : TROI ;         // Region of interest to be drawn (IN)
          ROINum : Integer ;   // ROI index number (IN)
          Canvas : TCanvas     // Image canvas to be drawn on (OUT)
          ) ;
// -----------------------------------
// Draw region of interest on to image
// -----------------------------------
const
    CrossHairSize = 12 ;
var
    OldColor : TColor ;
    OldStyle : TPenStyle ;
begin

     OldColor := Canvas.Pen.Color ;
     OldStyle := Canvas.Pen.Style ;

     if ROINum > 0 then begin
        Canvas.Pen.Style := psDot ;
        Canvas.Pen.Color := clWhite ;
        end
     else begin
        Canvas.Pen.Style := psSolid ;
        Canvas.Pen.Color := ROIColor ;
        end ;
     Canvas.Font.Color := Canvas.Pen.Color ;

     Case ROI.Shape of

          CrossHairsROI : begin
             // Draw cross-hairs
             Canvas.PolyLine( [Point(ROI.Centre.X, ROI.Centre.Y + CrossHairSize ),
                               Point(ROI.Centre.X, ROI.Centre.Y - CrossHairSize) ] ) ;
             Canvas.PolyLine( [Point(ROI.Centre.X + CrossHairSize, ROI.Centre.Y),
                               Point(ROI.Centre.X - CrossHairSize, ROI.Centre.Y) ] ) ;
             // Display index number
             if ROINum > 0 then Canvas.TextOut( ROI.Centre.X,
                                                 ROI.Centre.Y + CrossHairSize,
                                                 format('%d',[ROINum])) ;

             end ;

          RectangleROI : begin
             // Draw rectangle
             Canvas.Rectangle( ROI.TopLeft.X,     ROI.TopLeft.Y,
                               ROI.BottomRight.X, ROI.BottomRight.Y ) ;
             // Display index number
             if ROINum > 0 then Canvas.TextOut( ROI.BottomRight.X,
                                                 ROI.BottomRight.Y,
                                                 format('%d',[ROINum])) ;
             end ;

          EllipseROI : begin
             // Draw ellipse
             Canvas.Ellipse( ROI.TopLeft.X,     ROI.TopLeft.Y,
                             ROI.BottomRight.X, ROI.BottomRight.Y ) ;
             // Display index number
             if ROINum > 0 then Canvas.TextOut( ROI.Centre.X,
                                                 ROI.BottomRight.Y,
                                                 format('%d',[ROINum])) ;
             end ;

          LineROI : begin
             // Draw line
             Canvas.Polyline( [ROI.TopLeft,ROI.BottomRight] ) ;
             // Display index number
             if ROINum > 0 then Canvas.TextOut( ROI.BottomRight.X,
                                                 ROI.BottomRight.Y,
                                                 format('%d',[ROINum])) ;
             end ;

          end ;

     Canvas.Pen.Color := OldColor ;
     Canvas.Font.Color := OldColor ;
     Canvas.Pen.Style := OldStyle ;
     // Display ROI index number

     end ;


procedure TViewFrm.ScaleROI(
          const SrcROI : TROI ;      // Source ROI (IN)
          var DestROI : TROI         // Destination ROI (OUT)
          ) ;
// --------------------------------------------
// Make scaled copy of region of interest array
// --------------------------------------------
var
    i : Integer ;
begin

         DestROI.InUse := SrcROI.InUse ;
         DestROI.Shape := SrcROI.Shape ;

//         DestROI.NumPoints := SrcROI.NumPoints ;
//         for i := 0 to SrcROI.NumPoints-1 do begin
//             DestROI.XY[i].X := Round(SrcROI.XY[i].X*DisplayZoom) ;
//             DestROI.XY[i].Y := Round(SrcROI.XY[i].Y*DisplayZoom) ;
//            end ;

         DestROI.TopLeft.x := Round((SrcROI.TopLeft.x - sbXScroll.Position)*DisplayZoom) ;
         DestROI.TopLeft.y := Round((SrcROI.TopLeft.y - sbYScroll.Position)*DisplayZoom) ;

         DestROI.BottomRight.x := Round((SrcROI.BottomRight.x - sbXScroll.Position)*DisplayZoom) ;
         DestROI.BottomRight.y := Round((SrcROI.BottomRight.y - sbYScroll.Position)*DisplayZoom) ;

         DestROI.Centre.x := Round((SrcROI.Centre.x - sbXScroll.Position)*DisplayZoom) ;
         DestROI.Centre.y := Round((SrcROI.Centre.y - sbYScroll.Position)*DisplayZoom) ;

         DestROI.Width := Round(SrcROI.Width*DisplayZoom) ;
         DestROI.Height := Round(SrcROI.Height*DisplayZoom) ;

         end ;


procedure TViewFrm.UnScaleROI(
          const SrcROI : TROI ;      // Source ROI (IN)
          var DestROI : TROI   // Destination ROI (OUT)
          ) ;
// --------------------------------------------
// Make unscaled copy of region of interest array
// --------------------------------------------
var
    i : Integer ;
begin

         DestROI.InUse := SrcROI.InUse ;
         DestROI.Shape := SrcROI.Shape ;

         DestROI.TopLeft.x := Round((SrcROI.TopLeft.x/DisplayZoom) + sbXScroll.Position) ;
         DestROI.TopLeft.y := Round((SrcROI.TopLeft.y/DisplayZoom) + sbYScroll.Position) ;

         DestROI.BottomRight.x := Round((SrcROI.BottomRight.x/DisplayZoom) + sbXScroll.Position) ;
         DestROI.BottomRight.y := Round((SrcROI.BottomRight.y/DisplayZoom) + sbYScroll.Position) ;

         DestROI.Centre.x := Round((SrcROI.Centre.x/DisplayZoom) + sbXScroll.Position) ;
         DestROI.Centre.y := Round( (SrcROI.Centre.y/DisplayZoom) + sbYScroll.Position) ;

         DestROI.Width := Round(SrcROI.Width/DisplayZoom) ;
         DestROI.Height := Round(SrcROI.Height/DisplayZoom) ;

//         DestROI.NumPoints := SrcROI.NumPoints ;
//         for i := 0 to SrcROI.NumPoints-1 do begin
//             DestROI.XY[i].X := Round(SrcROI.XY[i].X/DisplayZoom) ;
//             DestROI.XY[i].Y := Round(SrcROI.XY[i].Y/DisplayZoom) ;
//             end ;

     end ;


procedure TViewFrm.FormClose(Sender: TObject; var Action: TCloseAction);
// --------------------------------------------
// Free allocated resources when form is closed
// --------------------------------------------
begin

     // Save current set of ROIs
     SaveROIsToFile( DefaultROIFile ) ;

     // Close currently open file
     if ImagePropertiesChanged then begin
        ImageFile.XResolution := edXResolution.Value ;
        ImageFile.YResolution := edYResolution.Value ;
        ImageFile.ZResolution := edZResolution.Value ;
        ImageFile.TResolution := edTResolution.Value ;
        end ;
     ImageFile.CloseFile ;

     // Free allocated resources
     FreeMem( PFrameBuf ) ;
     FreeMem( PDisplayBuf ) ;
     FreeMem( PWorkBuf ) ;
     //FreeMem( LUT ) ;
     Bitmap.Free ;

     // Indicate that this form is no longer active
     MainFrm.ViewFrmsInUse[Index] := False ;

     Action := caFree ;

     end;


procedure TViewFrm.cbDisplayZoomChange(Sender: TObject);
// -----------------------------
// New image display zoom factor
// -----------------------------
begin

     DisplayZoom := 0.01*Integer(cbDisplayZoom.Items.Objects[cbDisplayZoom.ItemIndex]) ;

     Resize ;

     CurrentFrame := -1 ;

     invalidate ;

     end;


procedure TViewFrm.FormResize(Sender: TObject);
// -------------------------------------------
// Update control sizes when form size changed
// -------------------------------------------
begin

     Page.Width := Max(ClientWidth - Page.Left - 2,2) ;
     Page.Height := Max(ClientHeight - Page.Top - 2,2) ;
     ControlGrp.Height := Max(ClientHeight - ControlGrp.Top - 5,2) ;

     SetImageSize ;

     // Intensity histogram page
     Hist.Width :=  Max( HistogramPage.ClientWidth - Hist.Left - 5, 2) ;
     HistGrp.Width := Hist.Width ;

     HistGrp.Top := Max(HistogramPage.ClientHeight - HistGrp.Height - 2,2) ;
     lbHistCursor.Top := HistGrp.Top - lbHistCursor.Height - 2 ;
     Hist.Height := Max(lbHistCursor.Top - Hist.Top - 1,2) ;

     // Properties page
     PixelDimensionsGrp.Top := Max(PropertiesPage.Height - PixelDimensionsGrp.Height - 4,2) ;
     CalBarGrp.Top := PixelDimensionsGrp.Top ;

     meProperties.Width := Max(PropertiesPage.Width - meProperties.Left - 4,2) ;
     meProperties.Height := Max(PixelDimensionsGrp.Top - meProperties.Top - 2,2) ;

     CurrentFrame := -1 ;

     end;

procedure TViewFrm.SetImageSize ;
var
    AvailableWidth, AvailableHeight, i : Integer ;
begin

     AvailableWidth := ImagePage.ClientWidth - sbYScroll.Width - 5 - Image.Left ;
     AvailableHeight :=  ImagePage.ClientHeight
                         - sbXScroll.Height
                         - lbImageCursor.Height - 5 - Image.Top ;

     DisplayZoom := 0.01*Integer(cbDisplayZoom.Items.Objects[cbDisplayZoom.ItemIndex]) ;
     Image.Width := Max(Min(AvailableWidth,Round(FrameWidth*DisplayZoom)),2) ;
     Image.Height := Max(Min(AvailableHeight,Round(FrameHeight*DisplayZoom)),2) ;
     BitMap.Width := Image.Width ;
     BitMap.Height := Image.Height ;

     sbXScroll.Top := Image.Top + Image.Height + 1 ;
     sbXScroll.Width := Image.Width ;
     sbYScroll.Height := Image.Height ;
     sbYScroll.Left := Image.Left + Image.Width + 1 ;

     // Image scroll bar range
     sbXScroll.Max := Max( FrameWidth - Round(Image.Width/DisplayZoom),1) ;
     sbYScroll.Max := Max( FrameHeight - Round(Image.Height/DisplayZoom),1) ;


     lbImageCursor.Top := sbXScroll.Top + sbXScroll.Height + 1 ;
     lbFrameInfo.Top := lbImageCursor.Top ;

     // Load internal ROI records from master records
     // scaled by display zoom factor
     for i := 0 to High(ROIs) do ScaleROI(ROIsUnscaled[i],ROIs[i]) ;

     end ;

procedure TViewFrm.edDisplayIntensityRangeKeyPress(Sender: TObject;
  var Key: Char);
// -----------------------------------
// Display intensity LUT range changed
// -----------------------------------
begin
     if Key = #13 then begin
        // Set upper/lower limits of display intensity range
        GreyLo := Round(edDisplayIntensityRange.LoValue) ;
        GreyHi := Round(edDisplayIntensityRange.HiValue) ;
        UpdateLUT ;
        CurrentFrame := -1 ;
        end;
     end;


procedure TViewFrm.ImageMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
// --------------------------------------------------
// Display pixel readout when cursor moved over image
// --------------------------------------------------
var
     iX,iY,iZ,iPixel : Integer ;
begin
     iX := Round(X/DisplayZoom) ;
     iY := Round(Y/DisplayZoom) ;

     if NumComponentsPerPixel = 1 then begin
        // Grey scale images
        iPixel := (iY*FrameWidth + iX)*NumComponentsPerPixel ;
        lbImageCursor.Caption := format( 'X=%.3g %s, Y=%.3g %s, I=%d            ',
                                 [iX*edXResolution.Value,cbPixelUnits.Text,
                                  (FrameHeight-iY)*edYResolution.Value,cbPixelUnits.Text,
                                  PIntArray(pDisplayBuf)^[iPixel]]) ;
        end
     else begin
        // RGB images
        iPixel := (iY*FrameWidth + iX)*NumComponentsPerPixel ;
        lbImageCursor.Caption := format( 'X=%.3g %s, Y=%.3g %s, R=%d, G=%d, B=%d            ',
                                 [iX*edXResolution.Value,cbPixelUnits.Text,
                                  (FrameHeight-iY)*edYResolution.Value,cbPixelUnits.Text,
                                  PIntArray(pDisplayBuf)^[iPixel],
                                  PIntArray(pDisplayBuf)^[iPixel+1],
                                  PIntArray(pDisplayBuf)^[iPixel+2]]) ;
        end ;

     lbImageCursor.Visible := True ;

     AdjustROI( X, Y ) ;

     end;


procedure TViewFrm.UpdateHistogram ;
// ---------------------------------
// Update image intensity histogram
// ---------------------------------
type
    TRuns = Array[0..4096*4096] of TRunElement ;
    PRuns = ^TRuns ;

const
     MaxPoints = 4096 ;

var
     NumPoints : Integer ; // No. of points in histogram plot
     BinWidth : single ;  // Width of histogram bin
     i,j : Integer ;
     xPix, yPix : Integer ;
     PixIntensity : Integer ;
     Histogram : Array[0..MaxPoints-1,0..MaxComponents-1] of Integer ;
     ROI : TROI ;
     iComp : Integer ;
     ISum : Single ;
     IValue : Single ;
     nSum : Integer ;
     nPixels : Integer ;
     iRun : Integer ;
     NumRuns : Integer ;
     Runs : PRuns ;
begin

     // Allocate storage
     GetMem( Runs, (NumPixelsPerFrame div 2)*SizeOf(TRunElement) ) ;

     Try

     NumPoints := MinInt( [GreyMax,MaxPoints] ) ;
     BinWidth := (GreyMax + 1) / NumPoints ;

     // Clear histograms
     for iComp := 0 to NumComponentsPerPixel-1 do begin
         for i := 0 to NumPoints do Histogram[i,iComp] := 0 ;
         end ;

     if cbHistROI.ItemIndex = 0 then begin
        // Histogram of whole image
        j := 0 ;
        for i := 0 to NumPixelsPerFrame-1 do begin
            for iComp := 0 to NumComponentsPerPixel-1 do begin
                PixIntensity := Trunc(PIntArray(pDisplayBuf)^[j]/BinWidth) ;
                PixIntensity := MaxInt([MinInt([PixIntensity,NumPoints]),0]) ;
                Histogram[PixIntensity,iComp] := Histogram[PixIntensity,iComp] + 1 ;
                Inc(j) ;
                end ;
            end ;

        end
     else begin
        // Histogram of region of interest

        GetROIPixelRuns( cbHistROI.ItemIndex, Runs^, NumRuns ) ;
        for iRun := 0 to NumRuns-1 do begin
            j := (Runs[iRun].y*FrameWidth + Runs[iRun].x)*NumComponentsPerPixel ;
            for i := 0 to Runs[iRun].Count - 1 do begin
                for iComp := 0 to NumComponentsPerPixel-1 do begin
                    PixIntensity := Trunc(PIntArray(pDisplayBuf)^[j]/BinWidth) ;
                    PixIntensity := MaxInt([MinInt([PixIntensity,NumPoints]),0]) ;
                    Histogram[PixIntensity,iComp] := Histogram[PixIntensity,iComp] + 1 ;
                    Inc(j) ;
                    end ;
                end ;
            end ;
        end ;

     Hist.xAxisAutoRange := False ;
     Hist.yAxisAutoRange := True ;
     { Create X and Y axes labels }
     Hist.xAxisLabel := 'Pixel intensity' ;
     Hist.yAxisLabel := 'No. pixels' ;

     Hist.ClearAllLines ;
     if NumComponentsPerPixel > 1 then begin
        // RGB image
        Hist.CreateLine( 0 , clRed, msNone, psSolid ) ;
        Hist.CreateLine( 1 , clGreen, msNone, psSolid ) ;
        Hist.CreateLine( 2 , clBlue, msNone, psSolid ) ;
        end
     else begin
        // Grey scale image
        Hist.CreateLine( 0 , clBlue, msNone, psSolid ) ;
        end ;

     Hist.xAxisMin := GreyLo ;
     Hist.xAxisMax := GreyHi ;
     Hist.XAxisTick := (GreyHi - GreyLo) / 5.0 ;

     Hist.ClearVerticalCursors ;
     HistCursorNum := Hist.AddVerticalCursor( clBlue, '' ) ;
     Hist.VerticalCursors[HistCursorNum] := HistCursorX ;

     for iComp := 0 to NumComponentsPerPixel-1 do begin
         for i := 0 to Numpoints-1 do begin
             Hist.AddPoint(iComp, i*BinWidth, Histogram[i,iComp])  ;
             end ;
         end ;

     // Calculate statistics
     ISum := 0.0 ;
     nSum := 0 ;
     IntensityMin := 1E30 ;
     IntensityMax  := -1E30 ;
     for iComp := 0 to NumComponentsPerPixel-1 do begin
         for i := 0 to NumPoints-1 do begin
             IValue := i*BinWidth ;
             nPixels := Histogram[i,iComp] ;
             if (nPixels > 0) then begin
                if IValue < IntensityMin then IntensityMin := IValue ;
                if IValue > IntensityMax then IntensityMax := IValue ;
                ISum := ISum + IValue*nPixels ;
                nSum := nSum + nPixels ;
                end ;
             end ;
         end ;

    if nSum > 0 then IntensityMean := ISum / nSum
                else IntensityMean := 0.0 ;

     // Compute standard deviation
     ISum := 0.0 ;
     for iComp := 0 to NumComponentsPerPixel-1 do begin
         for i := 0 to NumPoints-1 do begin
             IValue := (i*BinWidth) - IntensityMean ;
             nPixels := Histogram[i,iComp] ;
             if (nPixels > 0) then begin
                ISum := ISum + IValue*iValue*nPixels ;
                end ;
             end ;
         end ;
    if nSum > 1 then IntensitySD := Sqrt( ISum / (nSum-1) )
                else IntensitySD := 0.0 ;

    // Display statistics
    StatisticsGrp.Caption := format(' Statistics (%s) ',[cbHistROI.Text]) ;
    meStatistics.Clear ;
    meStatistics.Lines.Add( format( 'Mean= %.1f',[IntensityMean] ));
    meStatistics.Lines.Add( format( 'SD= %.1f',[IntensitySD] )) ;
    meStatistics.Lines.Add( format( 'Min.= %.1f',[IntensityMin] )) ;
    meStatistics.Lines.Add( format( 'Max.= %.1f',[IntensityMax] )) ;

    finally
        FreeMem( Runs ) ;
        end ;
     end ;


function TViewFrm.GetROIPixelRuns(
         iROI : Integer ;                 // Region of interest #
         var Runs : Array of TRunElement ;// Pixel runs
         var NumRuns : Integer            // No. pixel runs
         ) : Single ;
// --------------------------------------------------
// Return run-length coded array of pixels within ROI
// --------------------------------------------------
var
     LeftEdge : Integer ;  // Rectangular bounds of current ROI
     RightEdge : Integer ;
     TopEdge : Integer ;
     BottomEdge : Integer ;
     ROI : TROI ;
     x : Integer ;
     y : Integer ;
     aSq,bSq : Single ;
     r : Single ;
     Slope : Single ;
     XStart, XEnd : Single ;
     YStart, YEnd : Single ;
begin

     // Get region of interest
     ROI := ROIsUnscaled[iROI] ;
     LeftEdge := MinInt([ROI.TopLeft.X,ROI.BottomRight.X]) ;
     RightEdge := MaxInt([ROI.TopLeft.X,ROI.BottomRight.X]) ;
     TopEdge := MinInt([ROI.TopLeft.Y,ROI.BottomRight.y]) ;
     BottomEdge := MaxInt([ROI.TopLeft.Y,ROI.BottomRight.y]) ;

     case ROI.Shape of

         // Cross-hairs region of interest
         CrossHairsROI : begin
            NumRuns := 1 ;
            Runs[0].x := ROI.Centre.X ;
            Runs[0].y := ROI.Centre.Y ;
            Runs[0].z := 0 ;
            Runs[0].Count := RightEdge - LeftEdge + 1 ;
            Runs[0].ObjectID := 1 ;
            end ;

         // Rectangular region of interest
         RectangleROI : begin
            NumRuns := 0 ;
            for y := TopEdge to BottomEdge do begin
                Runs[NumRuns].x := LeftEdge ;
                Runs[NumRuns].y := y ;
                Runs[NumRuns].z := 0 ;
                Runs[NumRuns].Count := RightEdge - LeftEdge + 1 ;
                Runs[NumRuns].ObjectID := 1 ;
                Inc(NumRuns) ;
                end ;
            end ;

         // Elliptical region of interest
         EllipseROI : Begin

            aSq := (LeftEdge - ROI.Centre.x)*(LeftEdge - ROI.Centre.x) ;
            bSq := (TopEdge - ROI.Centre.y)*(TopEdge - ROI.Centre.y) ;
            NumRuns := 0 ;
            for y := TopEdge to BottomEdge do begin
                Runs[NumRuns].Count := 0 ;
                Runs[NumRuns].ObjectID := 1 ;
                for x := LeftEdge to RightEdge do begin
                    r := ((x-ROI.Centre.x)*(x-ROI.Centre.x)/aSq) +
                         ((y-ROI.Centre.y)*(y-ROI.Centre.y)/bSq) ;
                    if r <= 1.0 then begin
                       if Runs[NumRuns].Count = 0 then begin
                          Runs[NumRuns].x := x ;
                          Runs[NumRuns].y := y ;
                          Runs[NumRuns].z := 0 ;
                          end ;
                       Runs[NumRuns].Count := Runs[NumRuns].Count + 1 ;
                       end ;
                    end ;
                Inc(NumRuns) ;
                end ;
            end ;

         // Line region of interest
         LineROI : Begin

                XStart := ROI.TopLeft.X ;
                YStart := ROI.TopLeft.Y ;
                XEnd := ROI.BottomRight.X ;
                YEnd := ROI.BottomRight.Y ;
                Slope := (YEnd - YStart)/(XEnd - XStart) ;

                // Plot line
                NumRuns := 0 ;
                for y := TopEdge to BottomEdge do begin
                    Runs[NumRuns].Count := 0 ;
                    Runs[NumRuns].ObjectID := 1 ;
                    for x := LeftEdge to RightEdge do begin
                        if Abs(y - ((x-XStart)*Slope + YStart)) <= 0.5 then begin
                           if Runs[NumRuns].Count = 0 then begin
                              Runs[NumRuns].x := x ;
                              Runs[NumRuns].y := y ;
                              Runs[NumRuns].z := 0 ;
                              end ;
                           Runs[NumRuns].Count := Runs[NumRuns].Count + 1 ;
                           end ;
                        end ;
                    Inc(NumRuns) ;
                    end ;
                end ;

         end ;

     end ;


procedure TViewFrm.PageChange(Sender: TObject);
// ----------------------------------------
// Updates display when tab page is changed
// ----------------------------------------
var
     i : Integer ;
begin
     if Page.ActivePage = HistogramPage then begin
        //cbHistROI.Items.Assign(cbDeleteROI.Items) ;
        UpdateHistogram ;
        CopyDataAvailable := True ;
        CopyImageAvailable := True ;
        end
     else if Page.ActivePage = PropertiesPage then begin
        meProperties.Clear ;
        for i := 0 to ImageFile.Properties.Count-1 do
            meProperties.Lines.Add(ImageFile.Properties.Strings[i]);
        end
     else begin
        // Image page
        CopyDataAvailable := False ;
        CopyImageAvailable := True ;
        CurrentFrame := -1 ;
        end ;

     end;


procedure TViewFrm.CopyDataToClipboard ;
// -----------------------------------------------------------------
// Copy the data in currently displayed graph/image to the clipboard
// -----------------------------------------------------------------
begin
     if Page.ActivePage = HistogramPage then Hist.CopyDataToClipboard ;
     end ;


procedure TViewFrm.CopyImageToClipboard ;
{ -----------------------------------------------------
  Copy active plot to clipboard as Windows metafile
  ----------------------------------------------------- }
begin

    if Page.ActivePage = HistogramPage then begin
       { Copy Histogram }
       PrintGraphFrm.Plot := Hist ;
       PrintGraphFrm.ToPrinter := False ;
       PrintGraphFrm.ShowModal ;
       if PrintGraphFrm.ModalResult = mrOK then Hist.CopyImageToClipboard ;
       end
    else begin
      // Copy bitmap image
      BitMap.SaveToClipboardFormat( ClipboardImageFormat,
                                    ClipboardImageData,
                                    ClipboardPalette ) ;
      Clipboard.SetAsHandle( ClipboardImageFormat,
                             ClipboardImageData ) ;
      end ;

     end ;


procedure TViewFrm.Print ;
{ ------------------
  Print active plot
  ------------------ }
const
     Margin = 250 ;
var
     SRect : TRect ;
     Scale : Single ;
begin

    if Page.ActivePage = HistogramPage then begin
       { Print Histogram }
       PrintGraphFrm.Plot := Hist ;
       PrintGraphFrm.ToPrinter := True ;
       PrintGraphFrm.ShowModal ;
       if PrintGraphFrm.ModalResult = mrOK then begin
          Hist.ClearPrinterTitle ;
          Hist.AddPrinterTitleLine(FileName);
          Hist.Print ;
          end ;
       end
    else begin
      // Print image
      Printer.BeginDoc ;
      Printer.Canvas.TextOut(Margin,Margin,FileName) ;

      Scale := (Printer.Canvas.ClipRect.Right
               - Printer.Canvas.ClipRect.Left - 2*Margin) /
               BitMap.Width ;

      SRect.Left := Margin ;
      SRect.Right := Round(BitMap.Width*Scale) + Margin ;
      SRect.Top := Margin + 100 ;
      SRect.Bottom := SRect.Top + Round(BitMap.Height*Scale) ;
      Printer.Canvas.StretchDraw(SRect,BitMap);
      Printer.EndDoc ;

      end ;

     end ;



procedure TViewFrm.HistCursorChange(Sender: TObject);
// ---------------------------------
// Intensity histogram cursor changed
// ---------------------------------
var
     x,y : Single ;
     Lab : Array[0..MaxComponents-1] of String ;
     iCol : Integer ;
     s : String ;
begin
     if NumComponentsPerPixel > 1 then begin
        Lab[0] := ', R= ' ;
        Lab[1] := ', G= ' ;
        Lab[2] := ', B= ' ;
        end
     else Lab[0] := ', n= ' ;

     for iCol := 0 to NumComponentsPerPixel-1 do begin
         Hist.GetPoint( iCol, Hist.FindNearestIndex(iCol,HistCursorNum),x,y ) ;
         if iCol = 0 then s := format('I=%.0f',[x]);
         s := s + format('%s%.0f',[Lab[iCol],y]) ;
         end ;
     lbHistCursor.Caption := s ;

     lbHistCursor.Left := Hist.XToCanvasCoord(
                          Hist.VerticalCursors[HistCursorNum] )
                          - (lbHistCursor.Width div 2) ;
     lbHistCursor.Visible := True ;
     
     end;


procedure TViewFrm.ImageMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
// -------------------------------
// Mouse button has been depressed
// -------------------------------
begin

     MouseXPosition := X ;
     MouseYPosition := Y ;
     MouseDown := True ;

     end;


procedure TViewFrm.ImageMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
// -------------------------------
// Mouse button has been released
// -------------------------------
begin
     MouseXPosition := X ;
     MouseYPosition := Y ;
     MouseDown := False ;
     end;


procedure TViewFrm.bAddROIClick(Sender: TObject);
// -----------------------------
// Create new region of interest
// -----------------------------
var
     i : Integer ;
begin

     // Get next free ROI array element
     i := 1 ;
     while (ROIs[i].InUse) and (i<High(ROIs)) do Inc(i) ;

     // Update element

     if (i < High(ROIs)) and (not ROIs[i].InUse) then begin

        // Set ROI to current live ROI setting
        ROIs[i]:= ROIs[0] ;
        ROIs[i].InUse := True ;
        ROIs[i].ZoomFactor := DisplayZoom ;
        UnscaleROI( ROIs[i], ROIsUnscaled[i] ) ;

        // Add ROI to deletion list
        cbDeleteROI.Items.Add(format('%d',[i])) ;
        cbDeleteROI.ItemIndex := 0 ;

        // Update contrast range & histogram ROI list
        UpdateHistogramROIList ;

        // Enable ROI deletion buttons
        bDeleteROI.Enabled := True ;
        bDeleteAllROI.Enabled := bDeleteROI.Enabled ;
        cbDeleteROI.Enabled := True ;

        // Update regions of interest
        UpdateROIs ;

        // Save current set of ROIs
        SaveROIsToFile( DefaultROIFile ) ;

        end ;

     end;


procedure TViewFrm.bDeleteROIClick(Sender: TObject);
// --------------------------------------
// Delete ROI selected from deletion list
// --------------------------------------
var
     i : Integer ;
     ROINum : Integer ;
begin

     if cbDeleteROI.Text <> '' then begin
        // Delete selected ROI
        ROINum := StrToInt(cbDeleteROI.Text) ;
        ROIs[ROINum].InUse := False ;
        ROIsUnscaled[ROINum].InUse := False ;

        // Update ROI deletion list
        cbDeleteROI.Clear ;
        for i := 1 to High(ROIs) do if ROIs[i].InUse then begin
            cbDeleteROI.Items.Add(format('%d',[i])) ;
            end ;
        cbDeleteROI.ItemIndex := -1 ;

        // Update contrast range ROI list
        UpdateHistogramROIList ;

        // Update regions of interest
        UpdateROIs ;

        if cbDeleteROI.Items.Count < 1 then begin
           bDeleteROI.Enabled := False ;
           bDeleteAllROI.Enabled := bDeleteROI.Enabled ;
           cbDeleteROI.Enabled := False ;
           end ;

        // Save current set of ROIs
        SaveROIsToFile( DefaultROIFile ) ;

        end ;

     end;


procedure TViewFrm.bDeleteAllROIClick(Sender: TObject);
// ----------------------------------
// Delete all currently defined ROIs
// ----------------------------------
var
     i : Integer ;
begin

     for i := 1 to High(ROIs) do begin
         ROIs[i].InUse := False ;
         ROIsUnscaled[i].InUse := False ;
         end ;

     cbDeleteROI.Clear ;
     cbHistROI.Clear ;
     bDeleteROI.Enabled := False ;
     bDeleteAllROI.Enabled := bDeleteROI.Enabled ;
     cbDeleteROI.Enabled := False ;

     // Update contrast range ROI list
     UpdateHistogramROIList ;

     // Update regions of interest
     UpdateROIs ;

     // Save current set of ROIs
     SaveROIsToFile( DefaultROIFile ) ;

     end;

procedure TViewFrm.cbROITypeChange(Sender: TObject);
// --------------------------------
// Change region of interest shape
// --------------------------------
begin
     ROIs[0].Shape := TROIShape(
                      cbROIType.Items.Objects[cbROIType.ItemIndex] ) ;
     UnscaleROI( ROIs[0], ROIsUnscaled[0] ) ;
     UpdateROIs ;                 
     end;


procedure TViewFrm.ImageStatistics ;
// ------------------------------------
// Calculate image intensity statistics
// ------------------------------------
var
     IPix : Integer ;
     i : Integer ;
     IResid : Single ;
     Sum : Single ;
     OK : Boolean ;
     NewFrame : Integer ;
begin

    // Selected frame #
    NewFrame := sbSectionNum.Position + (sbStackNum.Position-1)*NumSectionsPerStack ;

     if CurrentFrame <> NewFrame then begin
        // Load frame
        OK := LoadFrame( NewFrame, PDisplayBuf ) ;
        end ;

     Sum := 0.0 ;
     IntensityMin := GreyMax ;
     IntensityMax := 0 ;
     for i := 0 to NumComponentsPerFrame-1 do begin
         IPix := PIntArray(PDisplayBuf)^[i] ;
         Sum := Sum + IPix ;
         if IPix < IntensityMin then IntensityMin := IPix ;
         if IPix > IntensityMax then IntensityMax := IPix ;
         end ;
     IntensityMean := Sum / NumComponentsPerFrame ;

     Sum := 0.0 ;
     for i := 0 to NumComponentsPerFrame-1 do begin
         IResid := PIntArray(PDisplayBuf)^[i] - IntensityMean ;
         Sum := Sum + (IResid*IResid) ;
         end ;
     IntensitySD := sqrt( Sum/(NumComponentsPerFrame-1) ) ;

     meStatistics.Clear ;
     meStatistics.Lines.Add( format( 'Mean= %.1f',[IntensityMean] ));
     meStatistics.Lines.Add( format( 'SD= %.1f',[IntensitySD] )) ;
     meStatistics.Lines.Add( format( 'Min.= %.1f',[IntensityMin] )) ;
     meStatistics.Lines.Add( format( 'Max.= %.1f',[IntensityMax] )) ;

     end ;


procedure TViewFrm.SetFrame(
          FrameNum : Integer ) ;
// ----------------------
// Set Frame # on display
// ----------------------
begin
     if (FrameNum > 0) and (FrameNum <= NumFrames) then begin
        sbStackNum.Position := ((FrameNum-1) div NumSectionsPerStack) + 1 ;
        sbSectionNum.Position := FrameNum
                                 - (sbStackNum.Position-1)*NumSectionsPerStack ;
        UpdateImage( FrameNum ) ;
        end ;
     end ;


procedure TViewFrm.SetStackNum(
          StackNum : Integer ) ;
// ----------------------
// Set Stack # on display
// ----------------------
var
     NewFrame : Integer ;
begin
     if (StackNum >= sbstackNum.Min) and (stackNum <= sbstackNum.Max) then begin
        sbstackNum.Position := stackNum ;
        NewFrame := sbSectionNum.Position + (sbStackNum.Position-1)*NumSectionsPerStack ;
        UpdateImage( NewFrame ) ;
        end ;
     end ;


procedure TViewFrm.SetSectionNum(
          SectionNum : Integer ) ;
// ----------------------
// Set section # on display
// ----------------------
var
     NewFrame : Integer ;
begin
     if (SectionNum >= sbSectionNum.Min) and (SectionNum <= sbSectionNum.Max) then begin
        sbSectionNum.Position := SectionNum ;
        NewFrame := sbSectionNum.Position + (sbStackNum.Position-1)*NumSectionsPerStack ;
        UpdateImage( NewFrame ) ;
        end ;
     end ;


procedure TViewFrm.SetNumSectionsPerStack( NumSections : Integer ) ;
// -----------------------------
// Set no. of sections per stack
// -----------------------------
begin
    edNumSectionsPerStack.Value := NumSections ;
    Set4DControls ;
    end ;


procedure TViewFrm.SetPalette( iPalette : TPaletteType ) ;
// -----------------------------
// Set display colour palette
// -----------------------------
var
      i : Integer ;
begin
     for i := 0 to cbPalette.Items.Count-1 do
         if TPaletteType(cbPalette.Items.Objects[i]) = palFalseColor then
            cbPalette.ItemIndex := i ;

     if NumComponentsPerPixel = 1 then begin
        SetBMapPalette( BitMap,
                    TPaletteType(cbPalette.Items.Objects[cbPalette.ItemIndex])) ;
        CurrentFrame := -1 ;
        end ;

     end ;

procedure TViewFrm.bLoadROisClick(Sender: TObject);
// ----------------------------------------
// Load region of interests definition file
// ----------------------------------------
begin

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

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

     OpenDialog.Filter := format(' %s Files (*.%s)|*.%s',
                          [ROIFileExtension,ROIFileExtension,ROIFileExtension]);
     OpenDialog.Title := 'Load ROIs from File' ;
     if OpenDialog.Execute then begin
        LoadROIsFromFile( OpenDialog.FileName ) ;
        // Save new set of ROIs to default file
        SaveROIsToFile( DefaultROIFile ) ;
        end ;

     end;


procedure TViewFrm.LoadROIsFromFile(
          FileName : String
          ) ;
// ---------------------------------------
// Load regions of interest from .ROI file
// ---------------------------------------
var
    FileHandle : Integer ;
    i : Integer ;
    LiveROI : TROI ;
begin

    // Save Live ROI setting
    LiveROI := ROIsUnscaled[0] ;

    // Open file
    FileHandle := FileOpen( FileName, fmOpenRead ) ;
    if FileHandle < 0 then begin
       MainFrm.StatusBar.SimpleText := ' Unable to load : ' + FileName ;
       Exit ;
       end ;

    // Read ROI record from file
    if FileRead( FileHandle, ROIsUnScaled, SizeOf(ROIsUnScaled))
       <> SizeOf(ROIsUnScaled) then
       MainFrm.StatusBar.SimpleText := ' Error reading : ' + FileName ;

    // Close file
    FileClose( FileHandle ) ;

    // Restore live ROI setting
    ROIsUnScaled[0] := LiveROI ;

    // Disable any ROIs which lie outside current frame
    for i := 1 to High(ROIsUnScaled) do if ROIsUnScaled[i].InUse then begin
        if (ROIsUnScaled[i].TopLeft.x >= FrameWidth) or
           (ROIsUnScaled[i].TopLeft.y >= FrameHeight) or
           (ROIsUnScaled[i].BottomRight.x >= FrameWidth) or
           (ROIsUnScaled[i].BottomRight.y >= FrameHeight) then
           ROIsUnScaled[i].InUse := False ;
        end ;

    // Load internal ROI records from master records
    // scaled by display zoom factor
    for i := 0 to High(ROIs) do ScaleROI(ROIsUnscaled[i],ROIs[i]) ;

    // Force display update
    CurrentFrame := -1 ;

    end ;


procedure TViewFrm.bSaveROIsClick(Sender: TObject);
// ----------------------------------------
// Save region of interests definition file
// ----------------------------------------
begin

     //SaveDialog.options := [ofPathMustExist] ;
     SaveDialog.DefaultExt := ROIFileExtension ;

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

     SaveDialog.Filter := format(' %s Files (*.%s)|*.%s',
                          [ROIFileExtension,ROIFileExtension,ROIFileExtension]);
     SaveDialog.Title := 'Save ROIs to File ' ;
     if SaveDialog.Execute then SaveROIsToFile( SaveDialog.FileName ) ;

     end;


procedure TViewFrm.SaveROIsToFile(
          FileName : String
          ) ;
// ---------------------------------------
// Save regions of interest from .ROI file
// ---------------------------------------
var
     FileHandle : Integer ;
begin
     // Create file
     FileHandle := FileCreate( FileName ) ;
     if FileHandle < 0 then begin
        MainFrm.StatusBar.SimpleText := ' Unable to save : ' + FileName ;
        Exit ;
        end ;

     // Save ROI record
     if FileWrite( FileHandle, ROIsUnScaled, SizeOf(ROIsUnScaled))
        <> SizeOf(ROIsUnScaled) then
        MainFrm.StatusBar.SimpleText := ' Error saving : ' + FileName ;

     // Close file
     FileClose( FileHandle ) ;
     end ;


procedure TViewFrm.SaveToFile(
          DestFileName : String ;       // Name of file to be created
          SavePixelDepth : Integer )  ; // No. of bits per pixel
// --------------------------------
// Make copy of currently open file
// --------------------------------
var
    Frame : Integer ;
    i : Integer ;
    PSrcBuf : Pointer ;
    PDestBuf : Pointer ;
    OK : Boolean ;
    SingleFrame : Boolean ;
begin

    // Allocate buffes
    GetMem( PSrcBuf, NumComponentsPerFrame*4 ) ;
    GetMem( PDestBuf, NumComponentsPerFrame*4 ) ;

  try

    if NumFrames > 1 then SingleFrame := False
                     else SingleFrame := True ;

    OK := SaveFile.CreateFile( DestFileName,
                               FrameWidth,
                               FrameHeight,
                               SavePixelDepth,
                               NumComponentsPerPixel,
                               SingleFrame ) ;
    SaveFile.XResolution := edXResolution.Value ;
    SaveFile.YResolution := edYResolution.Value ;
    SaveFile.ZResolution := edZResolution.Value ;
    SaveFile.TResolution := edTResolution.Value ;
    SaveFile.ResolutionUnit := ImageFile.ResolutionUnit ;

    if not OK then Exit ;

    for Frame := 1 to NumFrames do begin

        // Load image frame from source file
        LoadFrame( Frame, PSrcBuf ) ;

        // Copy to destination file
        // (Making pixel depth adjustment if necessary)

        if (PixelDepth > 8) and (SavePixelDepth <= 8) then begin
           // 16 bit source -> 8 bit destination
           for i := 0 to NumComponentsPerFrame-1 do
               PByteArray(PDestBuf)^[i] := PIntArray(PSrcBuf)^[i] shr 8 ;
           end
        else if (PixelDepth <= 8) and (SavePixelDepth > 8) then begin
           // 8 bit source -> 16 bit destination
           for i := 0 to NumComponentsPerFrame-1 do
               PWordArray(PDestBuf)^[i] := PIntArray(PSrcBuf)^[i] shl 8 ;
           end
        else if (PixelDepth  > 8) and (SavePixelDepth > 8) then begin
          // No change in pixel depth
           for i := 0 to NumComponentsPerFrame-1 do
               PWordArray(PDestBuf)^[i] := PIntArray(PSrcBuf)^[i] ;
           end
        else if (PixelDepth  <= 8) and (SavePixelDepth <= 8) then begin
          // No change in pixel depth
           for i := 0 to NumComponentsPerFrame-1 do
               PByteArray(PDestBuf)^[i] := PIntArray(PSrcBuf)^[i] ;
           end ;

        // Save image to destination
        SaveFile.SaveFrame( Frame, PDestBuf ) ;

        MainFrm.StatusBar.SimpleText := format(
        ' Creating file %s (%d bits per pixel) %d/%d',
        [DestFileName,PixelDepth,Frame,NumFrames] ) ;

        end ;

    // Close file
    SaveFile.CloseFile ;

    MainFrm.StatusBar.SimpleText := format(
        ' File: %s created (%d bits per pixel, %d frames)',
        [DestFileName,PixelDepth,NumFrames] ) ;


  finally
      // Free buffers
      FreeMem( PSrcBuf ) ;
      FreeMem( PDestBuf ) ;
      end ;

    end ;


procedure TViewFrm.cbPaletteChange(Sender: TObject);
// ---------------------------------
// Display LUT colour mapping change
// ---------------------------------
begin
     if NumComponentsPerPixel = 1 then begin
        SetBMapPalette( BitMap,
                    TPaletteType(cbPalette.Items.Objects[cbPalette.ItemIndex])) ;
        CurrentFrame := -1 ;
        end ;
     end;

procedure TViewFrm.rbHistWholeImageClick(Sender: TObject);
// ----------------------------------------------
// Whole image intensity histogram option clicked
// ----------------------------------------------
begin
     UpdateHistogram
     end;

procedure TViewFrm.rbROIClick(Sender: TObject);
// --------------------------------------
// ROI intensity histogram option clicked
// --------------------------------------
begin
     UpdateHistogram ;
     end;

procedure TViewFrm.cbHistROIClick(Sender: TObject);
// --------------------------------------
// Number of ROI intensity histogram
// --------------------------------------
begin
     UpdateHistogram
     end;

procedure TViewFrm.edXResolutionKeyPress(Sender: TObject; var Key: Char);
// -------------------------------
// Pixel X axis resolution changed
// -------------------------------
begin
     if Key = #13 then XResolution := edXResolution.Value ;
     ImagePropertiesChanged := True ;
     end;


procedure TViewFrm.bSetHistAxesClick(Sender: TObject);
{ -----------------------------------
  Set histogram axes range/law/labels
  -----------------------------------}
begin
     SetAxesFrm.Plot := Hist ;
     SetAxesFrm.Histogram := True ;
     SetAxesFrm.ShowModal ;
     Hist.Invalidate ;
     end;


procedure TViewFrm.rb3DClick(Sender: TObject);
begin
     Set4DControls ;
     end;

procedure TViewFrm.rb4DClick(Sender: TObject);
begin
     Set4DControls ;
     end;

procedure TViewFrm.edSectionNumKeyPress(Sender: TObject; var Key: Char);
// ---------------------------------
// Section # to be display changed
// ---------------------------------
begin
     if key = #13 then begin
        // Note. Ensure selected frame number is ALWAYS first frame in sequence
        sbSectionNum.Position := Round(edSectionNum.LoValue) ;
        edSectionNum.HiValue :=  sbSectionNum.Max ;
        // Note This triggers display of new image
        end ;
     end;

procedure TViewFrm.edStackNumKeyPress(Sender: TObject; var Key: Char);
// ---------------------------------
// Stack # to be display changed
// ---------------------------------
begin
     if key = #13 then begin
        // Note. Ensure selected frame number is ALWAYS first frame in sequence
        sbStackNum.Position := Round(edStackNum.LoValue) ;
        edStackNum.HiValue :=  sbStackNum.Max ;
        // Note This triggers display of new image
        end ;
     end;

procedure TViewFrm.edNumSectionsPerStackKeyPress(Sender: TObject;
  var Key: Char);
begin
     if key = #13 then begin
        NumSectionsPerStack := Round(edNumSectionsPerStack.Value) ;
        Set4DControls ;
        end ;
     end;

procedure TViewFrm.sbStackNumChange(Sender: TObject);
// -----------------
// Stack # changed
// -----------------
begin
     // Update frame number readout/edit box
     edStackNum.LoValue := sbStackNum.Position ;
     end;

procedure TViewFrm.edYResolutionKeyPress(Sender: TObject; var Key: Char);
// -------------------------------
// Pixel Y axis resolution changed
// -------------------------------
begin
     if Key = #13 then YResolution := edYResolution.Value ;
     ImagePropertiesChanged := True ;
     end;

procedure TViewFrm.edZResolutionKeyPress(Sender: TObject; var Key: Char);
// -------------------------------
// Pixel Z axis resolution changed
// -------------------------------
begin
     if Key = #13 then ZResolution := edZResolution.Value ;
     ImagePropertiesChanged := True ;
     end;

procedure TViewFrm.bForwardsClick(Sender: TObject);
// -----------------------------------------
// Play series of frame in forward direction
// -----------------------------------------
begin
     bForwards.Enabled := False ;
     end;

procedure TViewFrm.bBackwardsClick(Sender: TObject);
// -----------------------------------------
// Play series of frame in reverse direction
// -----------------------------------------
begin
     bBackwards.Enabled := False ;
     end;

procedure TViewFrm.bStopClick(Sender: TObject);
// --------------------
// Stop playing frames
// --------------------
begin
     bForwards.Enabled := True ;
     bBackwards.Enabled := True ;
     end;

procedure TViewFrm.bGoToStartClick(Sender: TObject);
// -----------------------------------------------
// Stop playing frames and move to start of series
// -----------------------------------------------
begin
     bForwards.Enabled := True ;
     bBackwards.Enabled := True ;
     sbStackNum.Position := 1 ;
     end;


procedure TViewFrm.bGoToEndClick(Sender: TObject);
// -----------------------------------------------
// Stop playing frames and move to end of series
// -----------------------------------------------
begin
     bForwards.Enabled := True ;
     bBackwards.Enabled := True ;
     sbStackNum.Position := sbStackNum.Max ;
     end;


procedure TViewFrm.ImageDblClick(Sender: TObject);
// --------------------
// Mouse double clicked
// --------------------
var
    iPixel : Integer ;
begin
     DblClickX := Round( MouseXPosition / DisplayZoom );
     DblClickY := Round( MouseYPosition / DisplayZoom );
     iPixel := (DblClickY*FrameWidth + DblClickX)*NumComponentsPerPixel ;
     DblClickPixelIntensity := PIntArray(pDisplayBuf)^[iPixel] ;
     DblClickStackNum := CurrentStack ;
     // Increment to next frame
     if sbStackNum.Position < sbStackNum.Max then
        sbStackNum.Position := sbStackNum.Position + 1 ;
     end;


procedure TViewFrm.edTResolutionKeyPress(Sender: TObject; var Key: Char);
// -------------------------------
// Pixel inter-frame interval changed
// -------------------------------
begin
     if Key = #13 then TResolution := edTResolution.Value ;
     ImagePropertiesChanged := True ;
     end;

function TViewFrm.GetXResolution : Single ;
// ----------------
// Get X resolution
// ----------------
begin
    Result := edXResolution.Value ;
    end ;

procedure TViewFrm.SetXResolution( Value : Single ) ;
// ----------------
// Set X Resolution
// ----------------
begin
     edXResolution.Value := Value ;
     end ;


function TViewFrm.GetYResolution : Single ;
// ----------------
// Get Y resolution
// ----------------
begin
    Result := edYResolution.Value ;
    end ;

procedure TViewFrm.SetYResolution( Value : Single ) ;
// ----------------
// Set Y Resolution
// ----------------
begin
     edYResolution.Value := Value ;
     end ;

function TViewFrm.GetZResolution : Single ;
// ----------------
// Get Z resolution
// ----------------
begin
    Result := edZResolution.Value ;
    end ;

procedure TViewFrm.SetZResolution( Value : Single ) ;
// ----------------
// Set Z Resolution
// ----------------
begin
     edZResolution.Value := Value ;
     end ;


function TViewFrm.GetTResolution : Single ;
// ----------------
// Get T resolution
// ----------------
begin
    Result := edTResolution.Value ;
    end ;

procedure TViewFrm.SetTResolution( Value : Single ) ;
// ----------------
// Set X Resolution
// ----------------
begin
     edTResolution.Value := Value ;
     end ;


procedure TViewFrm.cbPixelUnitsChange(Sender: TObject);
begin
     PixelUnits := cbPixelUnits.Text ;
     edXResolution.Units := PixelUnits ;
     edYResolution.Units := PixelUnits ;
     edZResolution.Units := PixelUnits ;
     edCalBarWidth.Units := PixelUnits ;
     ImagePropertiesChanged := True ;
     end;

procedure TViewFrm.sbXScrollChange(Sender: TObject);
// ----------------------------------
// Image left edge scroll bar changed
// ----------------------------------
begin
     CurrentFrame := -1 ;
     end;

procedure TViewFrm.sbYScrollChange(Sender: TObject);
// ----------------------------------
// Image left edge scroll bar changed
// ----------------------------------
begin
     CurrentFrame := -1 ;
     end;

end.


