unit Drvfun;
{ ==========================================================================
  WinWCP - Driving function convolution/deconvolution module
  (c) J Dempster, 1997, All Rights Reserved
  ==========================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls, Grids, global, shared, fileio, SSQUnit, maths;

type
  TDrvFunFrm = class(TForm)
    RecordGrp: TGroupBox;
    edRecordNum: TEdit;
    ckBadRecord: TCheckBox;
    sbRecordNum: TScrollBar;
    pbDisplay: TPaintBox;
    lbTMin: TLabel;
    lbTMax: TLabel;
    EqnGrp: TGroupBox;
    EdEquation: TEdit;
    ParametersTable: TStringGrid;
    Timer: TTimer;
    ckUserSetParameters: TCheckBox;
    ckReconvolution: TCheckBox;
    GroupBox2: TGroupBox;
    GroupBox8: TGroupBox;
    edRecRange: TEdit;
    rbAllRecords: TRadioButton;
    rbThisRecord: TRadioButton;
    rbRange: TRadioButton;
    cbChannel: TComboBox;
    bDoTransforms: TButton;
    bCancel: TButton;
    bAbort: TButton;
    Label4: TLabel;
    GroupBox1: TGroupBox;
    Label1: TLabel;
    edVhold: TEdit;
    Label3: TLabel;
    edVRev: TEdit;
    edProgress: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure sbRecordNumChange(Sender: TObject);
    procedure bCancelClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure cbChannelChange(Sender: TObject);
    procedure ckBadRecordClick(Sender: TObject);
    procedure bDoTransformsClick(Sender: TObject);
    procedure edVholdKeyPress(Sender: TObject; var Key: Char);
    procedure edVRevKeyPress(Sender: TObject; var Key: Char);
    procedure FormPaint(Sender: TObject);
  private
    { Private declarations }
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
    procedure DisplayRecord ;
    Procedure TransformRecords ;
  public
    { Public declarations }
  end;

var
  DrvFunFrm: TDrvFunFrm;

implementation

{$R *.DFM}

uses mdiform ;

const
     { Deconvolution parameter table rows/column definitions}
     clPar = 0 ;
     clDecon = 1 ;
     clRecon = 2 ;
     rwTitle = 0 ;
     rwTau1 = 1 ;
     rwAmp1 = 2 ;
     rwTau2 = 3 ;
     rwAmp2 = 4 ;
type
    TState = ( DoRecord, DoTransforms, EndOfTransforms, Idle) ;
    TTransformJob = record
                Running : Boolean ;
                StartAt : LongInt ;
                EndAt : LongInt ;
                RecordNum : LongInt ;
                end ;

var
   State : TState ;
   ADC : ^TIntArray ;
   YSig : ^TSingleArray ;
   YFunc : ^TsingleArray ;
   RecHeader : ^TRecHeader ;
   TransformJob : TTransformJob ;
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }
   VHold : single ; { Holding potential (mV) }
   VRev : single ; { reversal potential (mV) }

procedure TDrvFunFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                New(ADC) ;
                New(RecHeader) ;
                New(YSig) ;
                New(YFunc) ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                Dispose(RecHeader) ;
                Dispose(ADC) ;
                Dispose(YSig) ;
                Dispose(YFunc) ;
                BuffersAllocated := False ;
                end ;
             end ;
          end ;
     end ;


procedure TDrvFunFrm.FormCreate(Sender: TObject);
begin
     Main.mnDrivingFunction.enabled := false ;
     BuffersAllocated := False ;
     end;


procedure TDrvFunFrm.FormShow(Sender: TObject);
{ ---------------------------------------------------------
  Initialise controls and create buffers when form is shown
  ---------------------------------------------------------}
var
   ch : LongInt ;
begin
     { Create buffers }
     HeapBuffers( Allocate ) ;

     { Initialise Channel combo box }
     cbChannel.items := ChannelNames ;
     if cbChannel.ItemIndex < 0 then cbChannel.ItemIndex := 0 ;

     { Set up range of records to be displayed in averaging setup display }
     if fH.Numrecords > 0 then begin
        sbRecordNum.Max := fH.NumRecords ;
        sbRecordNum.Min := 1 ;
        sbRecordNum.Enabled := True ;
        sbRecordNum.Position := 1 ;
        edRecRange.text := format(' 1 - %d ', [FH.NumRecords]) ;
        end
     else begin
          edRecRange.text := 'None' ;
        end ;

     VHold := -90. ;
     edVHold.text := format( ' %.f mV', [VHold] ) ;
     VRev := 0. ;
     edVRev.text := format( ' %.f mV', [VRev] ) ;

     { Set button states }
     bAbort.enabled := False ;
     bDoTransforms.enabled := True ;
     bCancel.enabled := True ;
     Timer.Enabled := True ;
     end;


procedure TDrvFunFrm.TimerTimer(Sender: TObject);
begin
     { Execute any requested operations }
     case State of
          DoRecord : begin
                pbDisplay.canvas.FillRect( pbDisplay.canvas.ClipRect ) ;
                if FH.NumRecords > 0 then DisplayRecord ;
                State := Idle ;
                end ;
          DoTransforms : begin
                Screen.Cursor := crHourGlass ;
                TransformRecords ;
                end ;
          EndOfTransforms : begin
                SaveHeader( DrvfH ) ;
                State := Idle ;
                Screen.Cursor := crDefault ;
                bAbort.enabled := False ;
                bDoTransforms.enabled := True ;
                bCancel.enabled := True ;
                Close ;
                end ;
          Idle : begin
                if Screen.Cursor <> crDefault then Screen.Cursor := crDefault ;
                end ;
          end ;

     end;

procedure TDrvFunFrm.DisplayRecord ;
{ ===============================
  Display digitised signal record
  ===============================}

var
   i,j,MaxCh,ch,ChOffset,Rec,ChSel : LongInt ;
   x,y,dx,t,SumAmp : single ;
   xPix,yPix,nOffScreen,Temp0,Temp1,Par,nPars : Integer ;
   row,col : LongInt ;
   Name : string ;
   ParName : String[10] ;
   ParUnits : String[10] ;
   EquationList : TStringList ;
begin

     sbRecordNum.Max := fH.NumRecords ;
     sbRecordNum.Min := 1 ;
     sbRecordNum.Enabled := True ;
     fH.RecordNum := SbRecordNum.position ;

     { Read record data from file }
     GetRecord( fH, RecHeader^, fH.RecordNum, ADC^ ) ;

     ChSel := cbChannel.ItemIndex ;

     InitializeDisplay( Channel[ChSel], 1, RecHeader^, lbTMin,lbTMax, pbDisplay) ;

     { Erase display }
     EraseDisplay(pbDisplay) ;

     ChOffset := Channel[ChSel].ChannelOffset ;
     nOffScreen := 0 ;
     dx := Channel[ChSel].xScale ;
     x := Channel[ChSel].xScale*( -Channel[ChSel].xMin) + Channel[ChSel].Left ;
     for i := 0 to fH.NumSamples-1 do begin

         y := ADC^[(i*fH.NumChannels) + ChOffset ] ;
         xPix := Trunc(x) ;
         yPix := Trunc(Channel[ChSel].Bottom
                       - Channel[ChSel].yScale*(y - Channel[ChSel].yMin));

         if (xPix < Channel[ChSel].Left) or (Channel[ChSel].Right < xPix )
            or (yPix < Channel[ChSel].Top) or (Channel[ChSel].Bottom < yPix ) then begin
            xPix := MinInt([MaxInt([xPix,Channel[ChSel].Left]),Channel[ChSel].Right]) ;
            yPix := MinInt([MaxInt([yPix,Channel[ChSel].Top]),Channel[ChSel].Bottom]) ;
            nOffScreen := nOffScreen + 1 ;
            end
         else nOffScreen := 0 ;

         if (nOffScreen > 1) or (i=0) then pbDisplay.canvas.moveto(xPix,yPix)
                                      else pbDisplay.canvas.lineto(xPix,yPix);

         x := x + dx ;
         end ;

     { Plot best fitting equation }
     dx := Channel[ChSel].xScale ;
     x := Channel[ChSel].xScale*( -Channel[ChSel].xMin) + Channel[ChSel].Left ;
     pbDisplay.canvas.pen.color := clRed ;
     for i := 0 to fH.NumSamples-1 do begin
         t := (i - RecHeader^.Equation.TZeroCursor)*RecHeader^.dt ;
         if t >= 0. then y := MathFunc( RecHeader^.Equation, t )
                    else y := 0. ;
         y := (y/Channel[ChSel].ADCScale) + Channel[ChSel].ADCZero  ;
         xPix := Trunc(x) ;
         yPix := Trunc(Channel[ChSel].Bottom
                       - Channel[ChSel].yScale*(y - Channel[ChSel].yMin));
         if (xPix < Channel[ChSel].Left) or (Channel[ChSel].Right < xPix )
            or (yPix < Channel[ChSel].Top) or (Channel[ChSel].Bottom < yPix ) then begin
            xPix := MinInt([MaxInt([xPix,Channel[ChSel].Left]),Channel[ChSel].Right]) ;
            yPix := MinInt([MaxInt([yPix,Channel[ChSel].Top]),Channel[ChSel].Bottom]) ;
            nOffScreen := nOffScreen + 1 ;
            end
         else nOffScreen := 0 ;

         if (nOffScreen > 1) or (i=0) then pbDisplay.canvas.moveto(xPix,yPix)
                                      else pbDisplay.canvas.lineto(xPix,yPix);
         x := x + dx ;
         end ;

     pbDisplay.canvas.pen.color := Channel[ChSel].color ;

     { Display Channel Name }
     pbDisplay.Canvas.TextOut( Channel[ChSel].Left,
                               (Channel[ChSel].Top + Channel[ChSel].Bottom) div 2,
                                ' ' + Channel[ChSel].ADCName ) ;

     { Draw zero level cursor }
     DrawHorizontalCursor(pbDisplay,Channel[ChSel],Channel[ChSel].ADCZero) ;

     edRecordNum.text := format('Rec. %d/%d', [sbRecordNum.position,fH.NumRecords]);

     { Show whether record has been rejected by operator }
     if RecHeader^.Status = 'ACCEPTED' then ckBadRecord.checked := False
                                       else ckBadRecord.checked := True ;

     if RecHeader^.Equation.Available then begin

        nPars := GetNumEquationParameters( RecHeader^.Equation ) ;
        ParametersTable.options := [goFixedVertLine,goFixedHorzLine,
                                    goVertLine,goHorzLine,goEditing] ;
        { Table heading }
        ParametersTable.RowCount := 3 ;
        ParametersTable.ColCount := 3 ;
        ParametersTable.cells[clPar,rwTitle] := ' Par.   ' ;
        ParametersTable.cells[clDecon,rwTitle] := ' Deconvolution ' ;
        ParametersTable.cells[clRecon,rwTitle] := ' Reconvolution ' ;
        for Col := 0 to ParametersTable.ColCount-1 do
            ParametersTable.ColWidths[Col] := ParametersTable.canvas.TextWidth(
                                              ParametersTable.cells[Col,rwTitle]
                                              +'.');

        if (RecHeader^.Equation.EqnType = Exponential) or
           (RecHeader^.Equation.EqnType = Exponential2) then begin

           edEquation.text := 'exp( -t/Tau )' ;
           { Get time constant parameter info. }
           GetParameterInfo( RecHeader^.Equation, ParName, ParUnits,
                                Settings.TUnits,Channel[ChSel].ADCUnits, 2 ) ;
           { Time constant }
           ParametersTable.cells[clPar,rwTau1] := ParName ;
           ParametersTable.cells[clDecon,rwTau1] := format('%.3g',
                                         [Settings.TScale*RecHeader^.Equation.Par[2]] )
                                          + '  ' + ParUnits ;

           { Add second time constant and amplitudes, if two exponential fit }
           if RecHeader^.Equation.EqnType = Exponential2 then begin
              edEquation.text := 'A1*exp( -t/Tau1 ) + A2*exp( -t/Tau2 )' ;
              ParametersTable.RowCount := 5 ;
              { Amplitude 1 (normalised) }
              SumAmp := RecHeader^.Equation.Par[1] + RecHeader^.Equation.Par[3] ;
              GetParameterInfo( RecHeader^.Equation, ParName, ParUnits,
                                Settings.TUnits,Channel[ChSel].ADCUnits, 1 ) ;
              ParametersTable.cells[clPar,rwAmp1] := ParName ;
              ParametersTable.cells[clDecon,rwAmp1] := format('%.3g',
                                         [Settings.TScale*RecHeader^.Equation.Par[1]] )
                                          + '  ' + ParUnits ;
              { 2nd time constant }
              GetParameterInfo( RecHeader^.Equation, ParName, ParUnits,
                                Settings.TUnits,Channel[ChSel].ADCUnits, 4 ) ;
              ParametersTable.cells[clPar,rwTau2] := ParName ;
              ParametersTable.cells[clDecon,rwTau2] := format('%.3g',
                                         [RecHeader^.Equation.Par[4]] ) ;
              { Amplitude 2 (normalised) }
              GetParameterInfo( RecHeader^.Equation, ParName, ParUnits,
                                Settings.TUnits,Channel[ChSel].ADCUnits, 5 ) ;
              ParametersTable.cells[clPar,rwAmp2] := ParName ;
              ParametersTable.cells[clDecon,rwAmp2] := format('%.3g',
                                         [RecHeader^.Equation.Par[5]/SumAmp] ) ;
              end ;
           end
        else edEquation.text := ' None: deconvolution disabled';
        end ;
     end ;


Procedure TDrvFunFrm.TransformRecords ;
{ ==========================================
  Create a file containing driving functions
  ==========================================}
var
   i,j,ch,nFFT : LongInt ;
   iY,i0,i1 : LongInt ;
   ChOffset,nPars,ChSel : Integer ;
   YScale,YZero,y,YMin,YMax,t,VDrive,SumAmp : single ;
   YSigR,YSigI,YFuncR,YFuncI,Denominator : single ;
   Eqn : TEquation ;
begin

     { Read record data from file }
     GetRecord( fH, RecHeader^, TransformJob.RecordNum, ADC^ ) ;

     { Channel to be deconvoluted }
     ChSel := cbChannel.ItemIndex ;

     { if record is ACCEPTED ... process it }
     if (RecHeader^.Status = 'ACCEPTED') and
        ( (RecHeader^.Equation.EqnType = Exponential)
          or (RecHeader^.Equation.EqnType = Exponential) ) then begin

          { NOTE ... only exponential or 2-exponential functions
            are suitable for deconvolution }

         { Make the size of the transform buffer a power of 2 equal to
           (or greater than) the number of samples points in the record }
         nFFT := 128 ;
         repeat nFFT := nFFT*2 until nFFT >= FH.NumSamples ;
         { Fill the buffer with zero values }
         for i := 1 to 2*nFFT do YSig^[i] := 0. ;

         { Copy signal from ADC buffer and convert to scaled units }
         YScale := Channel[cbChannel.ItemIndex].ADCScale ;
         YZero :=  Channel[cbChannel.ItemIndex].ADCZero ;
         j := Channel[cbChannel.ItemIndex].ChannelOffset ;
         for i := 1 to FH.NumSamples do begin
             YSig^[i] := (ADC^[j] - YZero)*YScale ;
             j := j + FH.NumChannels ;
             end ;

         { Transform the signal to the frequency domain }
         RealFFT( YSig^, nFFT, 1 ) ;

         { Create the function which the signal is to be deconvolved from }
         Eqn := RecHeader^.Equation ;

         Eqn.Par[0] := 0. ; { Steady-state must be always be zero }

         { Get function parameters from parameter table
           if user entered parameters are selected }
         if ckUserSetParameters.checked then begin
            Eqn.Par[2] := ExtractFloat( ParametersTable.cells[clDecon,rwTau1],
                                     Eqn.Par[i]*Settings.TScale )/Settings.TScale ;
            { Add extra parameters for 2 exponential function }
            if Eqn.EqnType = Exponential2 then begin
               Eqn.Par[4] := ExtractFloat( ParametersTable.cells[clDecon,rwTau2],
                                          Eqn.Par[4]*Settings.TScale )
                                          /Settings.TScale ;
               Eqn.Par[1] := ExtractFloat( ParametersTable.cells[clDecon,rwAmp1],
                                          Eqn.Par[1] ) ;
               Eqn.Par[3] := ExtractFloat( ParametersTable.cells[clDecon,rwAmp2],
                                          Eqn.Par[3] ) ;
               end ;
            end ;

         { Adjust amplitudes to sum to unity }
         if Eqn.EqnType = Exponential then begin
            Eqn.Par[1] := 1. ;
            end
         else begin
            SumAmp := Eqn.Par[1] + Eqn.Par[3] ;
            If SumAmp = 0. then SumAmp := 1. ;
            Eqn.Par[1] := Eqn.Par[1] / SumAmp ;
            Eqn.Par[3] := Eqn.Par[3] / SumAmp ;
            end ;

         { Create the function }
         for i := 1 to 2*nFFT do begin
            t := (i-1)*RecHeader^.dt ;
            yFunc^[i] := MathFunc( Eqn, t )
            end ;

         { Transform the function to the frequency domain }
         RealFFT( YFunc^, nFFT, 1 ) ;

         { Divide the FFT of the signal by the FFT of the decay function
           using complex arithmetic }
           if Abs(YFunc^[1]) > 0. then YSig^[1] := YSig^[1] / YFunc^[1] ;
           if Abs(YFunc^[2]) > 0. then YSig^[2] := YSig^[2] / YFunc^[2] ;
           j := 3 ;
           for i := 2 to nFFT do begin
               YSigR := YSig^[j] ;
               YSigI := YSig^[j+1] ;
               YFuncR := YFunc^[j] ;
               YFuncI := YFunc^[j+1] ;
               Denominator := YFuncR*YFuncR + YFuncI*YFuncI ;
               if Abs(Denominator) > 1E-30 then begin
                  YSig^[j] := (YSigR*YFuncR + YSigI*YFuncI) / Denominator ;
                  YSig^[j+1] := (YSigI*YFuncR - YSigR*YFuncI) / Denominator ;
                  end
               else begin
                  YSig^[j] := 0. ;
                  YSig^[j+1] := 0. ;
                  end ;
               j := j + 2 ;
               end ;

         { RECONVOLUTION ===================================
           (If the reconvolution box is checked, convolute the
           driving function with an new exponential function
           =================================================}

         if ckReconvolution.checked then begin
           { Get function parameters from parameter table }
           Eqn.Par[0] := 0. ;
           Eqn.Par[1] := 1. ;
           Eqn.Par[2] := ExtractFloat( ParametersTable.cells[clRecon,rwTau1],
                                       Eqn.Par[2]*Settings.TScale )
                                       /Settings.TScale ;
           { Extra parameters for 2 exponential function }
           if Eqn.EqnType = Exponential2 then begin
              Eqn.Par[4] := ExtractFloat( ParametersTable.cells[clRecon,rwTau2],
                                          Eqn.Par[4]*Settings.TScale )
                                          /Settings.TScale ;
              Eqn.Par[1] := ExtractFloat( ParametersTable.cells[clRecon,rwAmp1],
                                          Eqn.Par[1] ) ;
              Eqn.Par[3] := ExtractFloat( ParametersTable.cells[clRecon,rwAmp2],
                                          Eqn.Par[3] ) ;
              end ;
           { Create function }
           for i := 1 to 2*nFFT do begin
               t := (i-1)*RecHeader^.dt ;
               yFunc^[i] := MathFunc( Eqn, t )
               end ;

           { Transform the function to the frequency domain }
           RealFFT( YFunc^, nFFT, 1 ) ;

           { Convolute the driving function with the exponential function }
           YSig^[1] := YSig^[1]*YFunc^[1] ;
           YSig^[2] := YSig^[2]*YFunc^[2] ;
           j := 3 ;
           for i := 2 to nFFT do begin
               YSigR := YSig^[j] ;
               YSigI := YSig^[j+1] ;
               YFuncR := YFunc^[j] ;
               YFuncI := YFunc^[j+1] ;
               YSig^[j] := YSigR*YFuncR - YSigI*YFuncI ;
               YSig^[j+1] := YSigI*YFuncR + YSigR*YFuncI  ;
               j := j + 2 ;
               end ;
           end ;

        { Transform the result back into the time domain }
        RealFFT( YSig^, nFFT, -1 ) ;

        { Divide by number of samples to get correct reverse transform }
        for i := 1 to nFFT do YSig^[i] := ySig^[i] / nFFT ;

        {Divide by the current driving force to get a conductance value }
        if not ckReconvolution.checked then begin
           VDrive := ExtractFloat(edVHold.text, -90. ) - ExtractFloat(edVRev.text, 0. ) ;
           if Abs(VDrive) > 1E-4 then for i := 1 to nFFT do YSig^[i] := YSig^[i]/VDrive ;

           { Create appropriate conductance units }
           if Contains('nA',Channel[ChSel].ADCUnits) then
                Channel[ChSel].ADCUnits := 'uS/ms'
           else if Contains('pA',Channel[ChSel].ADCUnits) then
                Channel[ChSel].ADCUnits := 'nS/ms'
           else if Contains('uA',Channel[ChSel].ADCUnits) then
                Channel[ChSel].ADCUnits := 'mS/ms'
           else Channel[ChSel].ADCUnits := '??/ms' ;

           { Find maximum and minimum of driving function }
           YMin := MaxSingle ;
           YMax := -MaxSingle ;
           for i := 0 to FH.NumSamples-1 do begin
               y := YSig^[i] ;
               if Y < YMin then YMin := Y ;
               if Y > YMax then YMax := Y ;
               end ;
           YScale := 2.*Abs(YMax - YMin) / MaxADCValue ;
           YZero := 0 ;
           end ;

        { Copy the driving function into the signal record replacing the
          orginal signal }
        j := cbChannel.ItemIndex ;
        for i := 1 to RawFH.NumSamples do begin
            ADC^[j] := Trunc(YSig^[i]/YScale + YZero );
            j := j + FH.NumChannels ;
            end ;

        { Save record to Transforms file  }
        Inc(DrvfH.NumRecords) ;
        PutRecord( DrvfH, RecHeader^, DrvfH.NumRecords, ADC^ ) ;
        end ;

     edProgress.text := format( '%d/%d', [TransformJob.RecordNum,TransformJob.EndAt]);

     Inc(TransformJob.RecordNum) ;
     { Terminate the job if that was the last record }

     if TransformJob.RecordNum > TransformJob.EndAt then begin
        TransformJob.RecordNum := TransformJob.EndAt ;
        { Request end-of-averaging processes }
        State := EndofTransforms ;
        end ;
     end ;


procedure TDrvFunFrm.sbRecordNumChange(Sender: TObject);
{ ----------------------------------------------------------
  Request a new record to be displayed when slider bar moved
  ----------------------------------------------------------}
begin
     State := DoRecord ;
     end;


procedure TDrvFunFrm.bCancelClick(Sender: TObject);
{ --------------
  Cancel Button
  -------------}
begin
     Close ;
     end;


procedure TDrvFunFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    { Turn off event scheduling timer }
     Timer.enabled := false ;
     { Dispose of buffers }
     HeapBuffers( Deallocate ) ;

     if (DrvFH.NumRecords > 0) and (DrvFH.FileName <> '') then begin
        Main.ShowDrivingFunction.visible := True ;
        Main.ShowDrivingFunction.Enabled := True ;
        Main.ShowDrivingFunction.Click ;
        end
     else Main.mnDrivingFunction.enabled := True ;
     Action :=caFree ;
     end;

procedure TDrvFunFrm.cbChannelChange(Sender: TObject);
begin
     State := DoRecord ;
     end;


procedure TDrvFunFrm.ckBadRecordClick(Sender: TObject);
{ ------------------------------------------------
  Save new record ACCEPTED/REJECTED status to file
  ------------------------------------------------}
begin
     if ckBadRecord.checked then RecHeader^.Status := 'REJECTED'
                            else RecHeader^.Status := 'ACCEPTED' ;
     PutRecordHeaderOnly( fH, RecHeader^, fH.RecordNum ) ;
     end;


procedure TDrvFunFrm.bDoTransformsClick(Sender: TObject);
{ -------------------
  Start transforms
  -------------------}
var
   OldHandle : Integer ;
begin
     if rbAllRecords.checked then begin
        { Analyse all records in file }
        TransformJob.StartAt := 1 ;
        TransformJob.EndAt := FH.NumRecords ;
        edRecRange.text := format( ' %d-%d', [TransformJob.StartAt,TransformJob.EndAt] ) ;
        end
     else if rbThisRecord.checked then begin
        { Analyse the currently displayed record }
        TransformJob.StartAt := FH.RecordNum ;
        TransformJob.EndAt := FH.RecordNum ;
        end
     else begin
          { Analyse the user entered range of records }
          GetIntRangeFromEditBox( edRecRange,
                                  TransformJob.StartAt,TransformJob.EndAt,
                                  1, fH.NumRecords ) ;
          end ;
     TransformJob.RecordNum := TransformJob.StartAt ;
     TransformJob.Running := True ;
     bDoTransforms.enabled := False ;
     bCancel.enabled := True ;
     Screen.cursor := crHourGlass ;

     { Copy details from original file header (except file handle!!!!) }
     OldHandle := DrvFH.FileHandle ;
     DrvfH := fH ;
     DrvFH.FileHandle := OldHandle ;

     { The driving functions data file name has the same name as the original file
       but with the extension .dfn }
     DrvfH.FileName := ReplaceFileEnding( fH.FileName, '.dfn' ) ;
     { Create file to hold averages }
     if DrvFH.FileHandle >= 0 then FileClose(  DrvFH.FileHandle ) ;
     DrvfH.FileHandle := FileCreate( DrvfH.FileName ) ;
     { Save header block and request}
     if DrvfH.FileHandle >= 0 then begin
           DrvfH.NumRecords := 0 ;
           SaveHeader( DrvfH ) ;
            State := DoTransforms ;
           end
     else MessageDlg( format('FileCreate Error = %d',[DrvfH.FileHandle]),
                      mtWarning, [mbOK], 0 ) ;

     end;

procedure TDrvFunFrm.edVholdKeyPress(Sender: TObject; var Key: Char);
{ ---------------------------------
  Update holding potential edit boc
  ---------------------------------}
begin
     if key = chr(13) then begin
        VHold := ExtractFloat( edVhold.text, VHold ) ;
        edVHold.text := format( ' %.f mV', [VHold] ) ;
        end ;
     end;

procedure TDrvFunFrm.edVRevKeyPress(Sender: TObject; var Key: Char);
{ ---------------------------------
  Update reversal potential edit boc
  ---------------------------------}
begin
     if key = chr(13) then begin
        VRev := ExtractFloat( edVRev.text, VRev ) ;
        edVRev.text := format( ' %.f mV', [VRev] ) ;
        end ;
     end;

procedure TDrvFunFrm.FormPaint(Sender: TObject);
begin
     if State = Idle then State := DoRecord ;
     end;

end.
