unit Spectrum;{exelogu}
{$I DEFINE.PAS}
interface
uses
  SysUtils, Classes,
  {$IFNDEF CONSOLE}
  Dialogs, Controls, Forms, Graphics,
  {$ENDIF}
  {v0.45}
  UtlType, WinUtl,
  {/v0.45
  TVType, MyType, MyLib,} Fileu,
  Msgu,
  {$IFNDEF CONSOLE}
  ShowMsg,
  {$ENDIF}
  UlanType, UlanGlob,
  ULRecTyp, ULObju, ULFObju,
  ULAType,  ULAObju,
  ULADType, ULADObju,
  ULAFType, ULAFObju,
  ULBType,  ULBObju,
  ULBRType, ULBRObju,
  ULEType,  ULEObju,
  ULPType,  ULPObju,
  ULPRType, ULPRObju,
  ULMType,  ULMObju,
  ULMFType, ULMFObju,
  ULVType,  ULVObju,
  ULVLType, ULVLObju,
  ULVOType, ULVOObju,
  ExpImpu
  {$IFNDEF CONSOLE}
  ,DebugFrm
  {$ENDIF}
  {v0.11}, MathCopr {/v0.11}
  {v0.13}
  ,ULIType, ULIObju
  {/v0.13}
  {v0.14}
  ,Channelsu
  {/v0.14}
  {v0.19}
  {$IFNDEF CONSOLE}
  ,infofrm
  {$ENDIF}
  {/v0.19}
  {v0.28}, Language {/v0.28}{v0.30}
  {$IFNDEF CONSOLE}
  ,FileMenuHdl
  {$ENDIF}
  {/v0.30}
  {v0.31}
  ,ULDRObju, Messages
  {/v0.31}
  {v0.43}, ExeLogu{/v0.43}
  ;

type
  {v0.11}
  ESpectrum = class(Exception);
  {/v0.11}
  {v0.13}
  EUserAborted = class(Exception);
  {/v0.13}
  {v0.14}
  EAcqData = class(Exception);
  {/v0.14}

type

  TProcPeak = record
    { Structure with data used for processing of one peak }
    PeakStart:TIndexPoint;
    Point_1,
    Point_2:TIndexPoint;
      { CurPoint-1, CurPoint-2 }
    LocMin_1, LocMax_1,
    LocMin_2, LocMax_2: TIndexPoint;
      { Last local (noise) extreme values. }
    CurMin, CurMax,
    Min_1, Max_1,
    Min_2, Max_2:TIndexPoint;
      { Last real extreme values. }
    IsInPeak:boolean;
  end;


  TSpectrum = class;

  {v0.24}
  TAcqData = class;

  TULADRc = class(TObject)
    ULAD: TULADObj;
    CurDataSize: integer;
    CurDataPos: integer;
    {v0.31}
    Addr:integer;
      {uLan net address of the device (detector) that gives the data}
    PointTimeInterval: single;
      { in what interval (in ms) come data points
        (default value = UlanGlob.UlanPointTimeInterval) }
    CurTime: single;
    YCoef: single;
      { used to multiply the Y value got from the device with (the result
        will be stored to the file) }
    {ulanglob}
    {/v0.31}
  protected
    function GetData: TStream;
  public
    constructor Create; reintroduce;
    property Data: TStream read GetData;
  end;

  TULADs = class(TList)
  private
    FAcqData: TAcqData;
  protected
    function GetULADRc(Index: integer): TULADRc;
    procedure AddULAD(AULAD: TULADObj);
    procedure DelULAD(AULAD: TULADObj);
    function FindULAD(AULAD: TULADObj; var Index: integer): boolean;
  public
    procedure Clear;override;
    constructor Create(AAcqData: TAcqDAta);
    destructor Destroy;override;
    procedure Update;
    procedure Reset;
    procedure SetZeroSizes;
    procedure CurDataSizeUpdate;
    {v0.31}
    procedure Start;
      { set curTime to zero for all ULADs }
    {/v0.31}
    property ULAD[Index: integer]: TULADRc read GetULADRc;default;
  end;

  TAcqData = class(TObject) { was apexpoints }
    { Analysis data - points }
    ULF: TULFObj;
      { TULFObj root; owner of ULA; created by reading FileName }
    ULA: TULAObj;
      { Pointer to Analysis object, owner of ULAD, ULAF, ULM }
    {v0.25}
    UsrDataIndex: integer;
    {/v0.25}
    {v0.24}
    ULADIndex: integer;
    ULAD: TULADs;
    {/v0.24
    ULAD: TULADObj;}
      { Pointer to Analysis raw Data object (using ULAD.Data: TStream);
        owned by ULA. }
    ULAF: TULAFObj;
      { Pointer to Analysis Filtered data object (using ULAF.Data: TStream);
        owned by ULA. }
    ULM: TULMObj;
      { Pointer to Method objects, owned by ULA, owner of ULMF }
    ULMF: TULMFObj;
      { Pointer to fiLter parameters (was FFilterPars: TFilterPars;) for
        filtering ULAD.Data to ULAF.Data; owned by ULA }
    ULM_ULP: TULPObj;
      { Method peaks (templates for autodetection of data peaks) }
    ULM_ULB: TULBObj;
      {  Method baseline records (in those baseline sections
         no peaks will be autodetected) }
    ULB: TULBObj;
      { Acquired Baseline sections }
    ULP: TULPObj;
      { Acquired Peaks }
    ULV: TULVObj;
      { Owner of view option records }
    ULVL: TULVLObj;
      { View limits option record }
    ULVO: TULVOObj;
      { View objects options }
    {v0.11}
    Cal_ULP: TULPObj;
      { Peaks from calibration file (specified in ULM.CalibrationFileName)
        used for calculation of responses, temporary object (doesn't get
        saved), can be nil, inicialized only when needed by reading from
        the file. }
    {v0.13}
    {v0.31 change to prop.}{/v0.31
    ULI: TULIObj;}
    {/v0.13}
    { FileName : string; tfilestream }
    { file for storing the data }
    SpectrumOpt: TSpectrumOpt;
    {ScreenDisp: TScreenDisp;
      { Size of graphic window where the drawing of points should be done }
    {v0.14}
    Spectrum: TSpectrum;
      { To which spectrum object are the data assigned right now
        (if any) }
    {/v0.14}
  private
    FMode: TOpenMode;
      { omCreate - creating = adding points; omRead, omReadOnly - reading points }
    CurIndex:Longint;
      { Current index used in sequential access using methods:
        Reset (or SeekIndex) and ReadIndexPoint }
    {v0.05}
    {BufPoints: array[0..MaxAvgPointCount-1] of TExpPoint;}
      { Buffer used by ReadPoint method to read ahead and eventually
        do the filtering. }
    {v0.20}{/v0.20 BufPointCount,}
      { Number of points in buffer. }
    {v0.20}{/v0.20 BufPointPos: integer;}
      { Index of point to be read next from the buffer. }
    { HeadSizeInFile:longint;
      { Set > 0 if .ULD file used; if .DAT file used, set to 0. }
    {Head:TUldRec;}
      { Used if HeadSizeInFile > 0. }
    UseFiltered:boolean;
      { If set, then public stream READING methods access filtered data
        in ULAF.Data. }
    {RawFileName: TFileName;
      { used in omCreate mode for storing acquired data; in omRead are
        the acquired data loaded into memory stream }
    {v0.11}
    FApplyMath: boolean;
      { should the mathematic operation be made on retrieved points? }
    FMultiplyX: TXValue;
    FMultiplyY: TYValue;
    FAddX: TXValue;
    FAddY: TYValue;
    {/v0.11}
    {v0.21}
    FCurDiluteCount: longint;
      { counter used in ReadPoint }
    FDiluteCount: longint;
      { default = 1, if > 1, then only every FDiluteCount'th data point will
        be considered }
    {/v0.21}
    {v0.20}
    {FCurDataPos: longint;}
      { Used in Reset, ReadPoint methods if ULAD.Data is TMemoryStream
        to access the memory directly (not through stream methods) }
    {FCurDataSize: longint;}
    {/v0.20}
    {v0.24}
    {$IFNDEF CONSOLE}
    FOwnerForm: TForm;
      { if non nil, then this form will destroy the data upon its destroying }
    {$ENDIF}
    {/v0.24}
    {v0.31}
    FChannel: TChannel;
      { assigned when aquiring data, to decide how many data lines to be aquired }
      {v0.41 also looked up if Channel property requested }{/v0.41}
    FULI: TULIObj;
    {/v0.31}
    {v0.34}
    FTemplateULADCount: integer;
    {/v0.34}
    {v0.45}
    FCreatedOK: boolean;
      { set at the end of constructor true, prevents saving incorrectly read
        file during destroy }
    {/v0.45}
    procedure Seek(APos:longint);
    function GetSize:longint;
    procedure ScanData;
      { Called by SetFileName, if some data present, finds extreme values
        of spectrum, calls Reset and then CheckPoint for each data point }
      { for stream; used just from destructor}
    procedure FixPoint(var APoint:TExpPoint);
      { Eventually fix APoint coordinates to fit in allowed Limits
        (if any is out) }
    {v0.31}
    {$IFNDEF CONSOLE}
    procedure CheckChannel;
    {$ENDIF}
      { used if open omCreate, pointer to channel which devices
        used to acquire the data, eventually increase number of ULADs
        accorging to number detector devices in the channel }
    procedure WMAppMessage(var Message: TMessage); message WM_APPMESSAGE;
    procedure SetULI(AULI: TULIObj);
    function GetDetAddrList: string;
    {/v0.31}
    {v0.41}
    {$IFNDEF CONSOLE}
    function GetChannel: TChannel;
    {$ENDIF}
    {/v0.41}
  protected
    {v0.16}
    function GetIsTemplate: boolean;
    {/v0.16}
    {v0.24}
    function GetRawFileName(Index: integer): string;
    function GetActiveData: TStream;
    function GetDataCount: integer;
    function GetDataIndex: integer;
    procedure SetDataIndex(Index: integer);
    function GetActiveULADRc: TULADRc;
    {/v0.24}
    {v0.25}
    function GetActiveDataName: string;
    procedure SetActiveDataName(const ADataName: string);
      { set ULADIndex accordingly to ADataName }

    function GetUsrActiveDataName: string;
    procedure SetUsrActiveDataName(const ADataName: string);
      { as SetActiveDataName, but also sets UsrDataIndex to the selected
        DataIndex }
    {/v0.25}
  public
    destructor Destroy; override;
    {v0.20}{/v0.20 procedure BufPointsClear;}
    {procedure SetFilterPars(AFilterPars:TULMFObj);
    procedure GetFilterPars(AFilterPars:TULMFObj);}
    {procedure HeadInit(var AHead:TUldRec);}
    {/v0.05}
    constructor Create(const AFileName: string; AMode: TOpenMode);
      { Name of the Analysis }
    {v0.09}
    constructor CreateFromPoints(APoints: PExpPoints; APointCount: integer);
    procedure CheckULRecords;
    {/v0.09}
    {v0.11}
    procedure CheckULMRecords;
    {/v0.11}
    procedure Reset;
      { Moves to the beggining of data (both ULAD, ULAF). }
      { Direct stream access, using current file position: }
    function WritePoint(var APoint:TExpPoint):boolean;
    function ReadPoint(var APoint:TExpPoint):boolean;
    {v0.05}
    function GetPointCount:longint;
    {/v0.05}
    procedure SeekPoint(Index:Longint);
      { = Seek(Index*sizeof(TExpPoint);
        if Index < 0, then makes Seek(ActivData.Position + sizeof(TExpPoint)*Index) }
    function ReadIndexPoint(var APoint:TIndexPoint):boolean;
    {/direct stream..}
    procedure Clear;
      { Deletes all points. }{v0.20}{Clears also baseline/peaks}{/v0.20}
    procedure ClearScanInfo;
      { Clears found extreme values, gets ready for ScanData or for
        AddPoint calls. }
    procedure CheckPoint(const APoint:TExpPoint);
      {.. called from ScanData or from AddPoint; updates Extremes }
    {procedure SetLimits(MinX: TXValue; MinY: TYValue; MaxX: TXValue; MaxY: TYValue);
      { What is the maximal allowed ViewLimit, exp. values outside
        these bounds are set to these values }
    procedure SetFileName(const AFileName: string; AMode: TOpenMode);
    {procedure SetUserLimit(AUserLimit: TUserViewLimit);}
      { xorigin, xsize, yorigin, ysize : real)}
    {v0.24}
    procedure ChangeFileName(const AFileName: string);
      { just change ULF.FileName without any immediate saving (used after
        starting from template, when name of the file to be acquired
        is already known (AAA). Extension will be automatically be changed
        to .ULF }
    {/v0.24}
    procedure AddPoint(var APoint: TExpPoint);
      { Called e.g. during acquisition to append new experimental point }
    procedure PutPoint(Index: TPointCount; APoint: TExpPoint);
      { Replace point at given index or if Index = GetPointCount append APoint }
    function GetPoint(Index: TPointCount; var APoint: TExpPoint): boolean;
    function GetIndexPoint(Index: TPointCount; var APoint: TIndexPoint): boolean;
      { Returns point with its coordinates plus its ordinal number in Data^,
       (false if Index out of range) }
    procedure SaveTo(const AFileName: string);
     { Saves current data to AFileName and set it as the current file. }
    function Current: Longint;
      { Returns position of last inserted item in mempage buffer }
    {v0.45 obsolete}
    {/v0.45
    procedure Error(Code,Info:integer);}
    procedure GrowShowLimits(dX: TScreenX; dY: TScreenY);
    procedure MoveShowLimits(dX: TScreenX; dY: TScreenY);
    {procedure SetScreenDisp(const AScreenDisp: TScreenDisp);
      { Size of graphic window used for conversion to graphic coordinates. }
    function ExpToScreen(const AExpPoint: TExpPoint; const AScreenDisp: TScreenDisp;
      var AScreenPoint: TScreenPoint): integer;
      { Converts datapoint to screenpoint (coordinates) using SpectrumOpt
        and ScreenDisp parameters. Fills AScreenPoint, returns 0 if expoint is
        on the screen, sets opLeft on Result if on the left from screen,
        opRight if on the right from screen.
        If X is in screen range, but Y out of it, the Y is adjusted to the
        Y limit value (and 0 is returned). }
   function ExpSizeToScreenSize(const AExpPoint:TExpPoint;
     const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): integer;
     { converts distance in exp units to distance in screen points }
    function ScreenToExp(const ScreenPoint: TScreenPoint; const AScreenDisp: TScreenDisp;
      var AExpPoint:TExpPoint): boolean;
      { Converts ScreenPoint to corresponding point in experimental coordinates,
        using SpectrumOpt and ScreenDisp parameters. }
    function FindDataPoint(const AExpPoint:TExpPoint;
      var ADataPoint:TExpPoint; var Index:TPointCount): boolean;
      { Finds closest point from collected data curve to the point
        AExpPoint (using x coordinate). }
    procedure SetViewLimit(const AViewLimit: TExpViewLimit);
    procedure SetLimit(const ALimit: TExpViewLimit);
    function UserToExp(const AUserPoint: TUserPoint; var AExpPoint: TExpPoint): boolean;
    function ExpToUser(const AExpPoint: TExpPoint; var AUserPoint: TUserPoint): boolean;
    function UserToScreen(const AUserPoint:TUserPoint;
      const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): boolean;
    function UserSizeToScreenSize(const AUserPoint:TUserPoint;
      const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): boolean;
    procedure FilterUpdate;
    {v0.11}
    procedure MethodLoadFromFile(const AFileName: string);
    procedure MethodSaveToFile(const AFileName: string);
    procedure CalibrationFileSelect(const AFileName: string);
    {v0.30}
    procedure SpectrumGet(var sp: TSpectrum);
      { checks if some Spectrum already assigned to Self, if yes returns
        pointer to it,if not creates one, returns pointer to it;
        SpectrumRelease MUST be called when sp not needed anymore }
    procedure SpectrumRelease(sp: TSpectrum);
      { MUST be called after SpectrumGet if sp not needed anymore }
    {/v0.30}

    procedure AcquiredDataDiscard;
    procedure DoMath(mo:TDataMathOperation; r: single);
      { set value of mo corresponding variable and activate FApplyMath field
        (or desactivate if mo = moNone) }
    procedure MakeMathPermanent;
      { save the values obtained by getpoint or readpoint method
        (eventyally modified if FApplyMath active) back to the file,
        then disable FApplyMath }
    {/v0.11}
    {v0.14}
    procedure InstrumentLoadFromFile(const AFileName: string);
    procedure InstrumentSaveToFile(const AFileName: string);
    function GetNonameFileName: string;
    {/v0.14}
    {v0.15}
    procedure GetReadyForTemplate;
      { Strip the file off (data, peaks, baseline sections) }
    {/v0.15}
    {v0.24}
    function DataSize: integer;
    {/v0.24}
    {v0.25}
    procedure UsrDataIndexReselect;
    {/v0.25}
    {v0.31}
    function GetAddrULADIndex(AAddr: integer): integer;
      { returns ULADIndex of ULADRc, that has given AAddr; if
        not such found - returns 0 (default index) }
    {/v0.31}

    {properties}
    property Mode: TOpenMode read FMode;
    {v0.11}
    property MultiplyX: TXValue read FMultiplyX;
    property MultiplyY: TYValue read FMultiplyY;
    property AddX: TXValue read FAddX;
    property AddY: TXValue read FAddY;
    procedure CreateTemplate(const AFileName: string);
      { as SetFileName but does not initialize ULAD,
        Free should be called as the last action. }
    {/v0.11}
    {v0.15}
    procedure Truncate(AfterX: TXValue);
      { remove all acqisition data (together with peaks, baselines)
        after specified X value }
    {/v0.15}

    {v0.30}
    function CanClose: boolean;{ulobjusru}
    {/v0.30}
    {/public methods}
    {v0.16}
    property IsTemplate: boolean read GetIsTemplate;
    {/v0.16}
    {v0.24}
    {$IFNDEF CONSOLE}
    property OwnerForm: TForm read FOwnerForm write FOwnerForm;
    {$ENDIF}
    property RawFileName[Index: integer]: string read GetRawFileName;
    property ActiveData: TStream read GetActiveData;
    property DataIndex: integer read GetDataIndex write SetDataIndex;
    property DataCount: integer read GetDataCount;
    property ActiveULADRc: TULADRc read GetActiveULADRc;
    {/v0.24}
    {v0.25}
    property ActiveDataName: string read GetActiveDataName write SetActiveDataName;
    property UsrActiveDataName: string read GetUsrActiveDataName write SetUsrActiveDataName;
    {/v0.25}
    {v0.31}
    property ULI: TULIObj read FULI write SetULI;
    property ULDetAddrList: string read GetDetAddrList;
    {/v0.31}
    {v0.41}
    {$IFNDEF CONSOLE}
    property Channel: TChannel read GetChannel;
    {$ENDIF}
    {/v0.41}
  end;


  TSpectrum = class(TObject)
    {SpectrumOpt:TSpectrumOpt;{set by init, parameters for calculation
      of peaks (used in FinishPeak method, eventually in AutoDetect)}
    AcqData: TAcqData;
      { Set by Create; data points to investigate. }
    Peaks: TULPObj;
      { Peaks, user defined or autodetected. TULPObj owned by AcqData.ULA. }
    BaseLine: TULBObj;
    {ReportData:TReportData;{report created during CalculatePeaksRatio}
    AutoDetectPeaks: boolean;
      { False by default; used in DoPeak method to decide if some kind
        of autodetection of peak end and peak start should be tried }
    ProcPeak: TProcPeak;
      { Parameters for processing current peak, i.e. the one started
        by StartPeak method and finished by FinshPeak method; inbetween
        some calls for DoPeekPoint should be called for all peaks between
        those two. }
    BLPeak: TProcPeak;
    {v0.11}
    LastResult: TSpectrumResult;
    {/v0.11}
    constructor Create(AData: TAcqData);
      { if non nil, overrides AData.SpectrumOpt}
    procedure SetData(AData: TAcqData);
    procedure OrderPoints(var P1, P2: TIndexPoint);
      { Make sure P1.X < P2.X }
    procedure DoPoint(const APoint: TIndexPoint);
      { Used in autodetect to find start and end of peak, calls
        StartPeak, DoPeakPoint and FinishPeak, when it think it's
        appropriate }
    procedure StartPeak(const APoint: TIndexPoint);
      procedure ClearProcPeak;
      procedure ClearBLPeak;
    procedure FinishPeak(const APoint: TIndexPoint);
      function CountHeight(var P1, P2: TIndexPoint; var X:TXValue): TYValue;
      function CountArea(var P1, P2: TIndexPoint): TAreaSize;
      function CountWidth(var P1, P2: TIndexPoint; H: TYValue): TXValue;

    procedure PeakCreate(const P1, P2: TIndexPoint);
      { Calls StartPeak(P1), then FinishPeak(P2); used usually when
        defining peaks  manually}

    procedure BaseLineCreate(const P1, P2: TIndexPoint);
      procedure StartBaseLine(const APoint: TIndexPoint);
      procedure FinishBaseLine(const APoint: TIndexPoint);
    procedure BaseLineMerge;
      { remove all inner points from neighbouring selected baseline sections -
        make just one section } 
    procedure BaseLineGroup;
      { makes selected neighbouring baseline sections grouped - move edges
        together }
    procedure BaseLineUngroup;
      { makes grouped baseline sections independent }
    procedure DoBaseLineLimitsChanged(b: TULBRObj);
      {should be called if some X value of b changed manually,
        calls UpdateBaseLineRecY and recalculate peaks that are above
        this baseline rec }
      procedure UpdateBaseLineRecY(b: TULBRObj);
      { finds corresponding Y values to b.X1,X2 values in data points }

    function AutoDetectOld: boolean;
    function AutoDetect: boolean;
      { Can be called if automatic detection if peaks is desired (ignores
        AutoDetectPeaks value). }
      function AutoDetectForBase: boolean;
      procedure BaseLineUpdateFromMethod;
        { Called after AutodetectFromBase to remove or split those
          autodetected baseline records, that would overlap with those
          defined in method ( - where peaks should not be autodetected) }
      {v0.25}
      procedure BaseLineRemoveDuplicates;
        { check for overlapped baseline records (added by autodetecting
          for additional wavelengths), and delete them if found }
      {/v0.25}
      function AutoDetectForPeaks: boolean;
    {procedure PeaksClear;}
      { Disposes all peaks. }
    procedure RecalculatePeaks;
      { Recalculates percentual content of peak areas; call after
        all peaks created or peak limits change }
      procedure RecalculatePeak(Peak: TULPRObj);
      procedure PeaksCalculateRatios;
    function IsScreenXInPeak(X: TScreenX; const AScreenDisp:TScreenDisp;
      var PeakIndex: integer): boolean;
    function IsScreenPointInPeakEdge(X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;

    function IsScreenPeakStart(X: TScreenX; const AScreenDisp: TScreenDisp;
       var PeakIndex: integer): boolean;
    function IsScreenPeakStop(X: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer): boolean;
    function MoveScreenPeak(OldStartX, NewStartX: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer): boolean;
    function SizeScreenPeak(OldStopX, NewStopX: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer; Right:boolean): boolean;
    procedure ToggleSelectScreenPeak(X: TScreenX; const AScreenDisp: TScreenDisp; Shift: TShiftState);
    procedure GetScreenPeaks(X: TScreenX; const AScreenDisp: TScreenDisp; var PrevPeak: TScreenPeak;
      var CurPeak: TScreenPeak; var NextPeak: TScreenPeak);
      { Get info about peaks around given screen X coordinate; if xPeak.X1 < 0 then
        no such peak found }

    procedure GetScreenBaseLineRec(X: TScreenX; const AScreenDisp: TScreenDisp;
      var CurIndex: integer; var PrevBL: TScreenPeak;
      var CurBL: TScreenPeak; var NextBL: TScreenPeak);
      { Get info about baseline records around given screen X coordinate; if xPeak.X1 < 0 then
        no such peak found }
    {v0.09}
    procedure GetScreenBaseLineNeighbours(Index:integer; const AScreenDisp: TScreenDisp;
      var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
      { Returns inc CurBL screen coordinates of Index baseline section, plus
        in PrevBL and NextBL coordinates of sections on the left and on the right }
    {/v0.09}
    function IsScreenXInBaseLine(X: TScreenX; const AScreenDisp:TScreenDisp;
      var PeakIndex: integer): boolean;
    function IsScreenPointInBaseLineEdge(var X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; var PeakIndex: integer; var LeftEdge:boolean): boolean;
    function MoveScreenBaseLine(OldStartX, NewStartX: TScreenX; const AScreenDisp: TScreenDisp;
      var Index: integer): boolean;
    function SizeScreenBaseLine(OldStopX, NewStopX: TScreenX; const AScreenDisp: TScreenDisp;
      var PIndex: integer; Right:boolean; SizingTwo:boolean): boolean;
    procedure ToggleSelectScreenBaseLine(X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; Shift: TShiftState);
    procedure BaseLineInsertPointFromScreen(const AScreenDisp: TScreenDisp; X: TScreenX);


    procedure PeaksFindYAndIndexValues;
    procedure PeakFindYAndIndexValues(p: TULPRObj{v0.26}; ReselectUsrData: boolean{/v0.26});
      function FindBaseY(const ADataName: string; X: TXValue; var Y: TYValue):boolean;

    procedure DoPeakLimitsChanged(p: TULPRObj);

    procedure CopySelectedPeaksToMethod;
    procedure CopySelectedBaseLineToMethod;
    procedure PeaksAssignParamsFromMethod;
    procedure PeakAssignParamsFromMethod(p: TULPRObj);
    {v0.25}
    function PeakFindPeakInMethodByX(x: TXValue; var mp: TULPRObj):boolean;
    function PeakFindInPeaksByX(pl: TULPObj; x: TXValue; var fp: TULPRObj): boolean;
    function PeakFindInPeaksInXRange(pl: TULPObj; const ADataName: string; x, xmin, xmax: TXValue; var fp: TULPRObj): boolean;

    {/v0.25}
    function PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;
    {v0.11}
    function PeakFindInPeaks(pl: TULPObj; p: TULPRObj; var fp: TULPRObj): boolean;
      { find in pl peaks the peak fp, that has p.X in fp's window interval }
    {/v0.11}
    {v0.12}
    function FindPeakByName(pl: TULPObj; const AName: TPeakName; var fp: TULPRObj): boolean;
     { find in pl peaks the peak fp, that has p.Name = AName }
    {/v0.12}


    destructor Destroy; override;

    {v0.09}
    function ULBRToScreenPeak(BL:TULBRObj; const AScreenDisp: TScreenDisp;
      var SP:TScreenPeak): integer;
    {/0.09}
    {v0.13}
    function PeaksAutoCalculate: integer;
      { calls either PeaksResponsesCalculate or PeaksAmountsCalculate,
        depending on CalibrationStandard switch value }
    {/v0.13}
    {v0.11}
    function PeaksResponsesCalculate: integer;
      { Calculates Response value for all peaks, returns <> 0 if invalid
        values encountered (zero Amount value) }
    function PeaksAmountsCalculate: integer;
      { called from menu, calls the following tho functions according to
        AcqData method settings }
      {v0.12}
      function PeaksAmountsCalculateCalibrationFile: integer;
        { [recalculate responses in calibration file,] assign responses }
      function PeaksAmountsCalculateInternalStandard: integer;
      function PeaksAmountsCalculateValues: integer;
      {/v0.12
      function PeaksAmountsCalculateFromCalibrationFile: integer;
      function PeaksAmountsCalculateFromInternalStandard: integer;}
    procedure PeakAssignPeakCharacterization(Dest, Src: TULPRObj);
      { assign to Dest peak all Src peak characterization values like name,
        response, groupname }

    procedure ClearResult;
    procedure SetResult(sr: TSpectrumResult; const msg:string);
    {/v0.11}

  end;


const
  DefSpectrumOpt:TSpectrumOpt =
    { Values usable for y = 0 - 255, and x = time }
  ( MaxPointCount: 0; {no limits for time}
    MaxPeakCount: 100; {we don't know, do we?}
    Trashold: 0;
    BaseLine: 0;
    PeakHeight:(Min:0; Max:0);
    PeakArea:(Min:0; Max:0);
    PeakWidth:(Min:0; Max:0);

    PeakStartLevel : 0;
    PeakEndLevel: 0;
    ViewLimit: (Min: (x: MinAcqXValue; y: MinAcqYValue);
                Max: (x: MaxAcqXValue; y: MaxAcqYValue));
    Limit: (Min: (x: MinAcqXValue; y: MinAcqYValue);
            Max: (x: MaxAcqXValue; y: MaxAcqYValue));
    {UserLimit: (Min: (x: 0; y: MinAcqYValue);
                Max: (x: 20; y: MaxAcqYValue));}
    PointCount:0;
    Extreme:(Min:(x:0;y:0); Max:(x:0;y:0))
  );

procedure CalculatePeaksRatios(Peaks:TULPObj);
function GetChannelFileName({v0.29}const ADir: string;{/v0.29}AChannelName: string): string;
{
procedure InitPacketRec(const ApexPacket:TApexPacket; var ApexRec:TApexPacketRec);
 }
const
  SpectrumBufferSize:longint = 1024;

{v0.36}
{$IFNDEF CONSOLE}
function FindOpenedData(const AFileName: string; var AAcqData: TAcqData{v0.36}; var AForm: TForm{/v0.36}): boolean;
{$ENDIF}
{/v0.36}

implementation
{$IFNDEF CONSOLE}
uses
  Main, SpecForm {v0.13}, AnalSetupFrm{/v0.13}
  {v0.14}, Modulu{/v0.14}{v0.45}, ChkULFu{/v0.45};
{$ENDIF}

{v0.33}
{$IFNDEF CONSOLE}
function FindOpenedData(const AFileName: string; var AAcqData: TAcqData{v0.36}; var AForm: TForm{/v0.36}): boolean;
var
  f: TForm;
  i: integer;
begin
  Result := false;
  for i := 0 to Screen.FormCount - 1 do begin
    f := Screen.Forms[i];
    if f is TSpectrumForm then with f as TSpectrumForm do begin
      if FindAcqData(AFileName, AAcqData) then begin
        {v0.36}
        AForm := f;
        {/v0.36}
        Result := true;
        exit;
      end;
    end;
  end;
end;
{$ENDIF}
{/v0.33}


{v0.28}
function CombineFactors(Factor, MFactor, DFactor: TFactor): TFactor;
{var
  f: TFactor;}
begin
  if Factor = 0 then
    Factor := 1;
  if MFactor = 0 then
    MFactor := 1;
  if DFactor = 0 then
    DFactor := 1;
  Result := Factor * MFactor / DFactor;
end;
{/v0.28}

function CountYOnLine(X: TXValue; X1: TXValue; Y1: TYValue;
  X2: TXValue; Y2: TYValue): TYValue;
begin
  if (X2 = X1) then
    Result := 0
  else
    Result := Y1 + (Y2 - Y1)/(X2 - X1) * (X - X1);
end;

function GetChannelFileName({v0.29}const ADir: string;{/v0.29}AChannelName: string): string;
begin
  GetChannelFileName := {v0.29}ADir + {/v0.29} '$$$' + AChannelName + '.$$$';
end;

{TPeaks}
procedure CalculatePeaksRatios(Peaks:TULPObj);
var
  a: TAreaSize;
  cnt, i: integer;
  p: TULPRObj;
  c: TUsrPeakCoef;
  {ulmtype}
begin
  a := 0;
  cnt := Peaks.ChildCount;
  for i := 0 to cnt - 1 do begin
    {v0.41}
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID = ULPRID then begin
      c := p.UsrPeakCoef;
      if c = 0 then
        c := 1;
      a := a + p.AreaSize * c;
    end;
    {/v0.41
    a := a + TULPRObj(Peaks.Childs[i]).AreaSize; }
  end;
  if a = 0 then
    exit;
  Peaks.DoChangeLock;
  try
    for i := 0 to cnt - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then begin
        {v0.41}
        c := p.UsrPeakCoef;
        if c = 0 then
          c := 1;
        p.Ratio := p.AreaSize * c/ a;
        {/v0.41
        p.Ratio := p.AreaSize / a;}
      end;
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;

{************************* TSpectrum ****************************}
constructor TSpectrum.Create(AData:TAcqData);
begin
  inherited Create;
  SetData(AData);
end;

procedure TSpectrum.SetData(AData: TAcqData);
begin
  if AData = nil then
    raise Exception.Create('TSpectrum.SetData AData = nil');
  {v0.14}
  if AcqData <> nil then
    AcqData.Spectrum := nil;
  AData.Spectrum := Self;
  {/v0.14}
  AcqData := AData;
  Peaks := TULPObj(AcqData.ULA.FindOrAdd(ULPID, ''));
    Peaks.SetFlag(rfCantDelete, true);
  BaseLine := TULBObj(AcqData.ULA.FindOrAdd(ULBID, ''));
    BaseLine.SetFlag(rfCantDelete, true);
end;

procedure TSpectrum.OrderPoints(var P1, P2:TIndexPoint);
  { Make sure P1.X < P2.X }
var P:TIndexPoint;
begin
  if P1.Point.X > P2.Point.X then begin
    P := P1;
    P1 := P2;
    P2 := P;
  end;
end;

procedure TSpectrum.StartPeak(const APoint: TIndexPoint);
begin
  ClearProcPeak;
  with ProcPeak do begin
    PeakStart := APoint;
    IsInPeak := true;
    CurMin := APoint;
    CurMax := APoint;
  end;
end;

function TSpectrum.CountHeight(var P1, P2: TIndexPoint; var X:TXValue): TYValue;
var
  h: TYValue;
  P: TIndexPoint;
  cnt: TPointCount;
begin
  {OrderPoints(P1, P2);}
  h := 0;
  cnt := AcqData.GetPointCount;
  AcqData.SeekPoint(P1.Index);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    if P.Point.Y > h then begin
      h := P.Point.Y;
      X := P.Point.X;
    end;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  CountHeight := h - AcqData.SpectrumOpt.BaseLine;
end;

function TSpectrum.CountArea(var P1, P2: TIndexPoint): TAreaSize;
var
  sum: TAreaSize;
  LP, P: TIndexPoint;
  cnt: TPointCount;
begin
  cnt := AcqData.GetPointCount;
  {OrderPoints(P1, P2);}
  LP := P1;
  sum := 0;
  if P1.Index <> P2.Index then begin
    AcqData.SeekPoint(P1.Index);
    repeat
      if not AcqData.ReadIndexPoint(P) then
        break;
      if P.Point.X < LP.Point.X then
        break;
      sum := sum + (P.Point.X - LP.Point.X) * (LP.Point.Y -
        AcqData.SpectrumOpt.BaseLine);
      LP := P;
      if P.Index = P2.Index then
        break;
      dec(cnt);
    until cnt = 0;
  end;
  CountArea := sum;
end;

function TSpectrum.CountWidth(var P1, P2: TIndexPoint; H: TYValue): TXValue;
  { Give H (peak height) if you know, just to save same time,
   otherwise set H=0 }
var
  LP, P: TIndexPoint;
  cnt: TPointCount;
  w, x: TXValue;
  w1, w2: TIndexPoint;
  hx:TXValue;{x at h}
begin
  CountWidth := 0;
  {OrderPoints(P1, P2);}
  if h <= 0 then
    h := CountHeight(P1, P2, hx);
  h := h / 2;
  if h < 0 then
    exit;
  x := (P2.Point.X - P1.Point.X) / 2;
  if x <= 0 then
    exit;
  cnt := AcqData.GetPointCount;{NumPoints;}
  w1.Index := NoIndexValue;
  w2.Index := NoIndexValue;
  P1.Point.Y := P1.Point.Y - AcqData.SpectrumOpt.BaseLine;
  LP := P1;
  AcqData.SeekPoint(P1.Index);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    P.Point.Y := P.Point.Y - AcqData.SpectrumOpt.BaseLine;
    if P.Point.Y > LP.Point.Y then begin
      if (LP.Point.Y <= h) and (P.Point.Y >= h) then
        if (P.Point.X <= x) and (W1.Index = NoIndexValue) then
          W1 := P
    end else if P.Point.Y < LP.Point.Y then begin
      if (LP.Point.Y >= h) and (P.Point.Y <= h) then
        if (P.Point.X >= x) then
          W2 := P;
    end;
    LP := P;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  w := 0;
  if W1.Index <> NoIndexValue then begin
    if W2.Index <> NoIndexValue then begin
      w:= W2.Point.x - W1.Point.x;
    end else
      w := 2 * (x - W1.Point.x);
  end else begin
    if W2.Index <> NoIndexValue then
      w := 2 * (W2.Point.x - x);
  end;
  CountWidth := w;
end;

procedure TSpectrum.FinishPeak(const APoint:TIndexPoint);
var
  Peak: TULPRObj;
  hw, hx: TXValue;
  h, a: TYValue;
  pe: TIndexPoint;

label
  ex;
begin
  pe := APoint;
  with ProcPeak do begin
    h := CountHeight(PeakStart, pe, hx);
    if (h < AcqData.SpectrumOpt.PeakHeight.Min) and
      (AcqData.SpectrumOpt.PeakHeight.Min <> 0) then
      goto ex;
    if (h > AcqData.SpectrumOpt.PeakHeight.Max) and
      (AcqData.SpectrumOpt.PeakHeight.Max <> 0) then
      goto ex;

    a := CountArea(PeakStart, pe);
    if (a < AcqData.SpectrumOpt.PeakArea.Min) and
       (AcqData.SpectrumOpt.PeakArea.Min <> 0) then
      goto ex;
    if (a > AcqData.SpectrumOpt.PeakArea.Max) and
       (AcqData.SpectrumOpt.PeakArea.Max <> 0) then
      goto ex;

    hw := CountWidth(PeakStart, pe, h);
    if (hw < AcqData.SpectrumOpt.PeakWidth.Min) and
       (AcqData.SpectrumOpt.PeakWidth.Min <> 0) then
      goto ex;
    if (hw > AcqData.SpectrumOpt.PeakWidth.Max) and
      (AcqData.SpectrumOpt.PeakWidth.Max <> 0) then
      goto ex;


    Peak := TULPRObj(Peaks.Add(ULPRID));
    Peak.DoChangeLock;
    try
      Peak.X1 := PeakStart.Point.X;
      Peak.Y1 := PeakStart.Point.Y;{Peak.P1 := PeakStart;}
      Peak.Index1 := PeakStart.Index;
      Peak.X2 := pe.Point.X;
      Peak.Y2 := pe.Point.Y;
      Peak.Index2 := pe.Index;
      {Peak^.P2 := pe;}
      Peak.X := hx;{CurMax.Point.X;}
      Peak.Height := h;
      Peak.Width := hw;
      Peak.AreaSize := a;

      DoPeakLimitsChanged(Peak);
      {v0.12}
      PeakAssignParamsFromMethod(Peak);
      {/v0.12}
    finally
      Peak.DoChangeUnlock;
    end;
    Peaks.Sort;
  ex:
    CurMin := pe;
    CurMax := pe;
    IsInPeak := false;
  end;
end;

procedure TSpectrum.RecalculatePeak(Peak: TULPRObj);
var
  p:TIndexPoint;
  maxy:TYValue;
  maxx:TXValue;
  a,a1:TAreaSize;
  x1,x2: TXValue;
  y1,y2: TYValue;
begin
  maxy := -$FFFFF;
  maxx := 0;
  a := 0;
  x1 := Peak.X1;
  y1 := Peak.BaseY1;
  x2 := Peak.X2;
  y2 := Peak.BaseY2;
  {v0.26}
  AcqData.ActiveDataName := Peak.DataName;
  {/v0.26}
  AcqData.SeekPoint(Peak.Index1);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    if P.Index > Peak.Index2 then
      break;
    if P.Point.Y > maxy then begin
      maxy := P.Point.Y;
      maxx := P.Point.X;
    end;
    a1 := P.Point.Y - CountYOnLine(P.Point.X, x1, y1, x2, y2);
    if a1 > 0 then
      a := a + a1;
    {(P.Point.X - LP.Point.X) * (LP.Point.Y - SpectrumOpt.BaseLine);}
  until false;

  Peak.X := maxx;
  if x1 <> x2 then
    Peak.Height := maxy - CountYOnLine(maxx, x1, y1, x2, y2);
    {AcqData.SpectrumOpt.BaseLine;}
  Peak.AreaSize := a;
  Peak.Width := Peak.X2 - Peak.X1;{not really; just for now}

  {wy := Data.SpectrumOpt.BaseLine + (Peak.Height / 2);}

  (*
  P1.Point.Y := P1.Point.Y - SpectrumOpt.BaseLine;
  LP := P1;
  Data.SeekPoint(P1.Index);
  repeat
    if not Data.ReadIndexPoint(P) then
      break;
    P.Point.Y := P.Point.Y - SpectrumOpt.BaseLine;
    if P.Point.Y > LP.Point.Y then begin
      if (LP.Point.Y <= h) and (P.Point.Y >= h) then
        if (P.Point.X <= x) and (W1.Index = NoIndexValue) then
          W1 := P
    end
    else
    if P.Point.Y < LP.Point.Y then begin
      if (LP.Point.Y >= h) and (P.Point.Y <= h) then
        if (P.Point.X >= x) then
          W2 := P;
    end;
    LP := P;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  w := 0;
  if W1.Index <> NoIndexValue then begin
    if W2.Index <> NoIndexValue then begin
      w:= W2.Point.x - W1.Point.x;
    end else
      w := 2 * (x - W1.Point.x);
  end else begin
    if W2.Index <> NoIndexValue then
      w := 2 * (W2.Point.x - x);
  end;
 *)
end;

procedure TSpectrum.RecalculatePeaks;
var
  cnt, i: integer;
begin
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  Peaks.DoChangeLock;
  try
    for i := 0 to cnt - 1 do begin
      RecalculatePeak(TULPRObj(Peaks.Childs[i]));
    end;
    PeaksCalculateRatios;
    Peaks.Sort;
    {v0.11}
    if AcqData.ULM.CalcAmounts then begin
      {v0.13}
      PeaksAutoCalculate;
      {/v0.13
      PeaksAmountsCalculate;}
    end;
    {/v0.11}
  finally
    Peaks.DoChangeUnlock;
    {v0.26}
    AcqData.UsrDataIndexReselect;
    {/v0.26}
  end;
end;

{procedure TSpectrum.DoPeakPoint(const APoint:TIndexPoint);
begin
end;}

procedure TSpectrum.BaseLineCreate(const P1, P2: TIndexPoint);
begin
  if P1.Index <= P2.Index then begin
    StartBaseLine(P1);
    FinishBaseLine(P2);
  end else begin
    StartBaseLine(P2);
    FinishBaseLine(P1);
  end;
end;

procedure TSpectrum.StartBaseLine(const APoint: TIndexPoint);
begin
  ClearBLPeak;
  with BLPeak do begin
    PeakStart := APoint;
    IsInPeak := true;
    CurMin := APoint;
    CurMax := APoint;
  end;
end;

procedure TSpectrum.FinishBaseLine(const APoint:TIndexPoint);
var
  BR: TULBRObj;
{  hw, hx: TXValue;
  h, a: TYValue;}
  pe: TIndexPoint;
label ex;
begin
  pe := APoint;
  with BLPeak do begin
    {h := CountHeight(PeakStart, pe, hx);
    if (h < AcqData.SpectrumOpt.PeakHeight.Min) and
      (AcqData.SpectrumOpt.PeakHeight.Min <> 0) then
      goto ex;
    if (h > AcqData.SpectrumOpt.PeakHeight.Max) and
      (AcqData.SpectrumOpt.PeakHeight.Max <> 0) then
      goto ex;

    a := CountArea(PeakStart, pe);
    if (a < AcqData.SpectrumOpt.PeakArea.Min) and
       (AcqData.SpectrumOpt.PeakArea.Min <> 0) then
      goto ex;
    if (a > AcqData.SpectrumOpt.PeakArea.Max) and
       (AcqData.SpectrumOpt.PeakArea.Max <> 0) then
      goto ex;

    hw := CountWidth(PeakStart, pe, h);
    if (hw < AcqData.SpectrumOpt.PeakWidth.Min) and
       (AcqData.SpectrumOpt.PeakWidth.Min <> 0) then
      goto ex;
    if (hw > AcqData.SpectrumOpt.PeakWidth.Max) and
      (AcqData.SpectrumOpt.PeakWidth.Max <> 0) then
      goto ex;
    }
    BR := TULBRObj(BaseLine.Add(ULBRID));

    BR.X1 := PeakStart.Point.X;
    BR.Y1 := PeakStart.Point.Y;{BR.P1 := PeakStart;}
    {BR.Index1 := PeakStart.Index;}
    BR.X2 := pe.Point.X;
    BR.Y2 := pe.Point.Y;
    {BR.Index2 := pe.Index;}
    {BR.X := hx;
    BR.Height := h;
    BR.Width := hw;
    BR.AreaSize := a;}
    DoBaseLineLimitsChanged(BR);

    BaseLine.Sort;

  ex:
    {CurMin := pe;
    CurMax := pe;}
    IsInPeak := false;
  end;
end;

function TSpectrum.MoveScreenBaseLine(OldStartX, NewStartX: TScreenX;
  const AScreenDisp: TScreenDisp; var Index: integer): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  p: TULBRObj;
  ap: TExpPoint;
{  d: TXValue;
  ap, op1,op2,
  np1,np2: TExpPoint;
  index1,index2: TPointCount;}
begin
  MoveScreenBaseLine := false;
  if not IsScreenXInBaseLine(OldStartX, AScreenDisp, pi) then
    exit;
{  if not IsScreenPeakStart(OldStartX, AScreenDisp, pi) then
    exit;}
  sp.X := NewStartX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);

  p.X1 := ap.X;
  DoBaseLineLimitsChanged(p);
  {UpdateBaseLineRecY(p);}


  {
  d := ap.X - p.X1;
  op1.X := p.X1 + d;;
  op1.Y := p.Y1;
  op2.X := p.X2 + d;
  op2.Y := p.Y2;
  if not AcqData.FindDataPoint(op1, np1, index1) then
    exit;
  if not AcqData.FindDataPoint(op2, np2, index2) then
    exit;
  p.X1 := np1.X;
  p.Y1 := np1.Y;
  p.X2 := np2.X;
  p.Y2 := np2.Y;
  }
  MoveScreenBaseLine := true;
end;

procedure TSpectrum.DoBaseLineLimitsChanged(b: TULBRObj);
var
  p: TULPRObj;
  i: integer;
begin
  UpdateBaseLineRecY(b);
  Peaks.DoChangeLock;
  try
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then begin
        if ((p.X1 >= b.X1) and (p.X1 <= b.X2)) or
           ((p.X2 >= b.X1) and (p.X2 <= b.X2)) then
        begin
          DoPeakLimitsChanged(p);
        end;
      end;
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;

procedure TSpectrum.UpdateBaseLineRecY(b: TULBRObj);
var
  op, np: TExpPoint;
  index: TPointCount;
begin
 {v0.25
  exit;}
  op.X := b.X1;
  op.Y := b.Y1;
  if AcqData.FindDataPoint(op, np, index) then begin
    b.X1 := np.X;
    b.Y1 := np.Y;
  end;
  op.X := b.X2;
  op.Y := b.Y2;
  if AcqData.FindDataPoint(op, np, index) then begin
    b.X2 := np.X;
    b.Y2 := np.Y;
  end;
end;

procedure TSpectrum.BaseLineInsertPointFromScreen(const AScreenDisp: TScreenDisp; X: TScreenX);
var
  pi: integer;
  sp: TScreenPoint;
  ap,np: TExpPoint;
  p: TULBRObj;
  n: TULBRObj;
  index: integer;
begin
  if not IsScreenXInBaseLine(X, AScreenDisp, pi) then
    exit;
  sp.X := X;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);
  n := TULBRObj(BaseLine.Add(ULBRID));
  n.X1 := np.X;
  n.Y1 := np.Y;
  n.X2 := p.X2;
  n.Y2 := P.Y2;
  n.GroupedLeft := true;
  p.X2 := np.X;
  p.Y2 := np.Y;
  p.GroupedRight := true;
  DoBaseLineLimitsChanged(p);
  DoBaseLineLimitsChanged(n);
  BaseLine.Sort;
end;

procedure TSpectrum.BaseLineMerge;
var
  i: integer;
  p, n: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      if (i + 1 < BaseLine.ChildCount) then begin
        n := TULBRObj(BaseLine.Childs[i + 1]);
        if n.IsFlagSet(rfSelected) then begin
          if n.CanDestroy then begin
            p.X2 := n.X2;
            p.Y2 := n.Y2;
            p.GroupedRight := n.GroupedRight;
            n.Free;
            DoBaseLineLimitsChanged(p);
            continue;
          end;
        end;
      end;
    end;
    inc(i);
  end;
end;

procedure TSpectrum.BaseLineGroup;
var
  i: integer;
  p, n: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      if (i + 1 < BaseLine.ChildCount) then begin
        n := TULBRObj(BaseLine.Childs[i + 1]);
        if n.IsFlagSet(rfSelected) then begin
          p.X2 := n.X1;
          p.Y2 := n.Y1;
          p.GroupedRight := true;
          n.GroupedLeft := true;
          DoBaseLineLimitsChanged(p);
          DoBaseLineLimitsChanged(n);
        end;
      end;
    end;
    inc(i);
  end;
end;

procedure TSpectrum.BaseLineUnGroup;
var
  i: integer;
  p{, n}: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      p.GroupedRight := false;
      p.GroupedLeft := false;
    end;
    inc(i);
  end;
end;

function TSpectrum.SizeScreenBaseLine(OldStopX, NewStopX: TScreenX;
  const AScreenDisp: TScreenDisp; var PIndex: integer; Right: boolean;
  SizingTwo: boolean): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULBRObj;
  np: TExpPoint;
  index: TPointCount;
begin
  SizeScreenBaseLine := false;
  if not IsScreenXInBaseLine(OldStopX, AScreenDisp, pi) then
    exit;
  {
  if not IsScreenPeakStop(OldStopX, AScreenDisp, pi) then
    exit;
  }
  sp.X := NewStopX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);
  if right then begin
    if p.X1 >= np.X{Index1 >= index} then begin
      {if p.CanDestroy then
        p.Free;}
      exit;
    end;
    p.X2 := np.X;
    p.Y2 := np.Y;
    {p.Index2 := index;}
  end else begin
    if p.X2 <= np.X{p.Index2 <= index} then begin
      {if p.CanDestroy then
        p.Free;}
      exit;
    end;
    p.X1 := np.X;
    p.Y1 := np.Y;
    {p.Index1 := index;}
  end;
  DoBaseLineLimitsChanged(p);

  if SizingTwo then begin
    { sizing effect also neighbour baseline section: }
    if right then begin
      { section on the right affected, i.e. new positin (np) is left edge of the section }
      inc(pi);
      if pi >= BaseLine.ChildCount then
        exit; { .. there is no more sections }
      p := TULBRObj(BaseLine.Childs[pi]);
      if np.X >= p.X2 then begin
        exit; {left limit can not be more or equal to the right limit of the section}
      end;
      p.X1 := np.X;
      p.Y1 := np.Y;
      {p.Index2 := index;}
    end else begin
      { section on the left affected: }
      dec(pi);
      if pi < 0 then
        exit;
      p := TULBRObj(BaseLine.Childs[pi]);
      if np.X <= p.X1 then begin
        exit;
      end;
      p.X2 := np.X;
      p.Y2 := np.Y;
      {p.Index1 := index;}
    end;
    DoBaseLineLimitsChanged(p);
  end;
  SizeScreenBaseLine := true;
end;


procedure TSpectrum.PeakCreate(const P1, P2: TIndexPoint);
begin
  if P1.Index <= P2.Index then begin
    StartPeak(P1);
    FinishPeak(P2);
  end else begin
    StartPeak(P2);
    FinishPeak(P1);
  end;
end;

procedure TSpectrum.DoPoint(const APoint: TIndexPoint);
begin
  with ProcPeak do begin
    if APoint.Point.Y > CurMax.Point.Y then begin
      CurMax := APoint;
      CurMin := APoint;
    end;

    if APoint.Point.Y < CurMin.Point.Y then
      CurMin := APoint;

    if AutoDetectPeaks then begin
      if not IsInPeak then begin
        {the peak was not started yet}
        if APoint.Point.Y > (AcqData.SpectrumOpt.BaseLine +
          AcqData.SpectrumOpt.PeakStartLevel) then
        begin
          {we came in peaks area}
          StartPeak(APoint);
        end else begin
          {we are under peaks area}
          if APoint.Point.Y > Point_1.Point.Y then begin
            {we are going up}
            if Point_1.Point.Y < Point_2.Point.Y then begin
              {but were going down before}
              {i.e. point_1 is local minimum, push min value}
              LocMin_2 := LocMin_1;
              LocMin_2 := Point_1;
    {
              if Point_1.Y < CurMin.Y then
                CurMin := Point_1;
            {  if Point_1.Y}
            end else begin
              {and were also going up or resting before}
              {i.e. the line is still raising below peeks level}
            end;
          end else begin
            {we are going down or resting below peeks level}
            if APoint.Point.Y < Point_1.Point.Y then begin
              {we are going down}
              if Point_1.Point.Y > Point_2.Point.Y then begin
                {but were going up before}
                {i.e. point_1 is local maximum, push max value}
                LocMax_2 := LocMax_1;
                LocMax_1 := Point_1;
              end else begin
                {and were also going down or resting before, below peeks level}
              end;
            end else begin
              {y did not change}
            end;
          end;
          {end of under peaks area}
        end;
      end else begin
        {we started to create a peak some time ago}
        if APoint.Point.Y <= (AcqData.SpectrumOpt.BaseLine +
          AcqData.SpectrumOpt.PeakEndLevel) then
        begin
          {we are leaving peaks area}
          FinishPeak(APoint);
        end else begin
          {we are in peaks area}
          if APoint.Point.Y > Point_1.Point.Y then begin
            {we are going up}
            if Point_1.Point.Y < Point_2.Point.Y then begin
              {but were going down before}
              {i.e. point_1 is local minimum, push min value}
              if Max_1.Point.Y - APoint.Point.Y > AcqData.SpectrumOpt.PeakHeight.Min then
              begin
                Min_2 := Min_1;
                Min_1 := Point_1;
                FinishPeak(Point_1);
              end;
              LocMin_2 := LocMin_1;
              LocMin_2 := Point_1;
            {  if Point_1.Y}
            end else begin
              {and were also going up before}
              {i.e. the line is still raising, and we are in the peek}
              if (APoint.Point.Y - CurMin.Point.Y) > AcqData.SpectrumOpt.PeakHeight.Min then
              begin
                {raised enough from the last minimum so that it could be
                 considered to be a new peak}
                if (CurMax.Point.Y - CurMin.Point.Y) > AcqData.SpectrumOpt.PeakHeight.Min then
                begin
                  {check also if the last extremes dif could belong to peak}
                  if CurMax.Point.X < CurMin.Point.X then
                    FinishPeak(CurMin);
                end;
              end;
            end;
          end else if APoint.Point.Y < Point_1.Point.Y then begin
            {we are going down}
            if Point_1.Point.Y > Point_2.Point.Y then begin
              {but were going up before}
              {i.e. point_1 is local maximum, push max value}
              LocMax_2 := LocMax_1;
              LocMax_1 := Point_1;
            end else if Point_1.Point.Y < Point_2.Point.Y then begin
                {and were also going down before}
            end;
          end;{else  y did not change}
          {end, in peak area}
        end;
        {end inpeak}
      end;

      Point_2 := Point_1;
      Point_1 := APoint;
      {if IsInPeak then
        DoPeakPoint(APoint);}
    end;{if AutoDetectPeaks}
  end;{with ProcPeak}
end;{DoPeak}

procedure TSpectrum.ClearBLPeak;
begin
  FillChar(BLPeak, sizeof(ProcPeak), 0);
end;

procedure TSpectrum.ClearProcPeak;
begin
  FillChar(ProcPeak, sizeof(ProcPeak), 0);
{
  Point_1.X := NoIndexValue;
  Point_2.X := NoIndexValue;
  LastLocExt.Min.X := NoIndexValue;
  LastLocExt.Max.X := NoIndexValue;
}
end;

procedure XYFilterGaus(points:PExpPoints;count:integer;S:single);
const
  Gbits=6;
  Gmask=(1 shl Gbits)-1;
var
  Gaus:array[0..Gmask]of TYValue;
  Yhist:array[0..Gmask]of TYValue;
  ip,ig:integer;
  Glen:integer;
  DX,X:TXValue;
  Gsum,Y:TYValue;
begin
  {v0.11}
  MathCoprMaskUnderflow; {No exception on rounding to zero}
  {/v0.11}
  if (count<16)or(S=0) then exit;
  DX:=(points^[count-1].X-points^[0].X)/(count-1);
  ig:=0; Gsum:=0; X:=0;
  repeat
    {v0.30 was causing floating point overflow}
    Y := 1/sqrt(2*pi);
    Y := Y/S;
    Y := Y*exp(-(X*X)/(S*S));
    {/v0.30
    Y:=1/sqrt(2*pi)/S*exp(-(X*X)/(S*S));}
    Gaus[ig]:=Y;
    Gsum:=Gsum+Y;
    inc(ig); X:=X+DX;
  until (ig>Gmask) or (Y<Gaus[0]*0.01);
  Glen:=ig-1;
  if Glen<=1 then exit;
  Y:=points^[0].Y;
  for ip:=0 to Gmask do Yhist[ip]:=Y;
  for ip:=0 to count-1 do
  begin
    Y:=points^[ip].Y;
    Yhist[ip and Gmask]:=Y;
    Y:=Y*Gaus[0];
    for ig:=1 to Glen do
    begin
      if ip+ig<count then
        Y:=Y+points^[ip+ig].Y*Gaus[ig]
      else
        Y:=Y+points^[count-1].Y*Gaus[ig];
      Y:=Y+Yhist[(ip-ig)and Gmask]*Gaus[ig];
    end;
    points^[ip].Y:=Y/Gsum;
  end;
end;

procedure XYDiff2Ord(points:PExpPoints;count:integer);
var
  i: integer;
  DiffX: TXValue;
  DiffA,DiffB: TYValue;
begin
  if count<3 then exit;
  DiffB:=0;
  for i:=0 to count-2 do
  begin
    DiffA:=DiffB;
    DiffX:=points^[i+1].X-points^[i].X;
    if DiffX=0 then DiffX:=0.001;
    DiffB:=(points^[i+1].Y-points^[i].Y)/DiffX;
    points^[i].Y:=DiffB-DiffA;
  end;
  points^[count-1].Y:=-DiffB;
end;

type
  BoolArray=array[0..MaxLongint div 8] of boolean;
  PBoolArray=^BoolArray;

{ Classify points as base or candidates for peaks
  and store result in isbase
    BaseMinInterval .. minimal length of couse satisfiing conditions
    BaseMaxDiff .. maximal absolute value of second order derivative
                   of course, if zero, automatic tuning is applied
}
procedure XYClassBase2Der(points:PExpPoints;count:integer;isbase:PBoolArray;
                      BaseMinInterval,BaseMaxDiff:single);
var
  diffp: PExpPoints;
  i, ib: integer;
  LastX: TXValue;
  MinDiff: TXValue;
  LocalMaxDiff: TXValue;
  zeropt,allpt: integer;
begin
  diffp:=nil;
  {v0.12}
  if Count <= 0 then
    exit;
  {/v0.12}
  try
    {Compute filtered second order derivative}
    GetMem(diffp,count*sizeof(TExpPoint));
    Move(points^,diffp^,count*sizeof(TExpPoint));
    XYDiff2Ord(diffp,count);
    XYFilterGaus(diffp,count,BaseMinInterval/2);
    {Find appropriate value when BaseMaxDiff is zero}
    if BaseMaxDiff=0 then
    begin
      MinDiff:=1e37;
      for i:=0 to count-1 do
      begin
        LocalMaxDiff:=0; ib:=i; zeropt:=0; allpt:=0;
        while (diffp^[ib].X-diffp^[i].X)<2*BaseMinInterval do
        begin
          inc(allpt);
          if diffp^[ib].Y=0 then inc(zeropt) else
            if LocalMaxDiff<abs(diffp^[ib].Y) then
              LocalMaxDiff:=abs(diffp^[ib].Y);
          inc(ib);
          if ib>count-1 then begin LocalMaxDiff:=1e37; break; end;
        end;
        if (LocalMaxDiff<MinDiff)and(zeropt<allpt/4) then
            MinDiff:=LocalMaxDiff;
      end;
      BaseMaxDiff:=10*MinDiff;
      (*Sum:=0;
      for i:=0 to count-1 do
      begin
        Sum:=Sum+Exp(-abs(diffp^[i].Y));
      end;
      BaseMaxDiff:=-Ln(Sum/count);*)
      DebLog('Computed BaseMaxDiff = '+FloatToStr(BaseMaxDiff));
    end;
    {Classify points}
    LastX:=diffp^[0].X; ib:=0;
    for i:=0 to count-1 do
    begin
      if abs(diffp^[i].Y)<=BaseMaxDiff then
      begin
        if (diffp^[i].X-LastX)>=BaseMinInterval then
        begin
          while ib<=i do
          begin
            isbase^[ib]:=true;
            inc(ib);
          end;
        end;
      end else begin
        LastX:=diffp^[i].X;
        while ib<=i do
        begin
          isbase^[ib]:=false;
          inc(ib);
        end;
      end;
    end;
  finally
    if diffp <> nil then
      FreeMem(diffp,count*sizeof(TExpPoint));
  end;
end;

{ Classify points as base or candidates for peaks
  and store result in isbase
    BaseMinInterval .. minimal length of couse satisfiing conditions
    BaseMaxSn .. maximal standart deviation of baseline course,
                 if zero, automatic tuning is applied
}
procedure XYClassBaseStdDev(points: PExpPoints; count: integer;
  isbase: PBoolArray; BaseMinInterval, BaseMaxStdDev: single);
var
  disp: PExpPoints;
  i, j, ib, iblock: integer;
  LastX, DX: TXValue;
  npoints: integer;
  SumY,SumY2:double;
  SumYNxt,SumY2Nxt:double;
  ADisp:single;
  BaseMaxDisp:single;

  {$IFNDEF CONSOLE}
  {m}f:TForm;{/m}
  {$ENDIF}
begin
  disp := nil;
  {v0.12}
  if Count <= 0 then
    exit;
  {/v0.12}
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    {/v0.11}
    {Consider constant sampling frequency -- It is not ptimal}
    DX:=(points^[count-1].X-points^[0].X)/(count-1);
    if DX=0 then exit;
    npoints:=round(BaseMinInterval/DX);
    if npoints<10 then npoints:=10;
    if count<2*npoints then exit;

    {Compute filtered standard deviation}
    GetMem(disp,count*sizeof(TExpPoint));
    Move(points^,disp^,count*sizeof(TExpPoint));
    XYFilterGaus(disp,count,BaseMinInterval/4);

    {Find appropriate value when BaseMaxStdDev is zero}
    if BaseMaxStdDev=0 then
    begin
      BaseMaxStdDev:=1e-5;
    end;
    BaseMaxDisp:=Sqr(BaseMaxStdDev)*Sqr(npoints);

    {Compute disperse for moving window
     Computation must be restarted after short block,
     because float format can accumulate precission errors
     A+B+C-A-B-C <> 0 }
    SumYNxt:=0;SumY2Nxt:=0;
    i:=0;
    while i<npoints do
    begin
      SumYNxt:=SumYNxt+disp^[i].Y; SumY2Nxt:=SumY2Nxt+Sqr(disp^[i].Y);
      inc(i)
    end;
    j:=0;
    while i<count do
    begin
      iblock:=i+npoints;
      if iblock>count then iblock:=count;
      SumY:=SumYNxt;SumY2:=SumY2Nxt;
      SumYNxt:=0;SumY2Nxt:=0;
      repeat
        ADisp:=SumY2*npoints-Sqr(SumY);
        SumY:=SumY-disp^[j].Y; SumY2:=SumY2-Sqr(disp^[j].Y);
        SumY:=SumY+disp^[i].Y; SumY2:=SumY2+Sqr(disp^[i].Y);
        SumYNxt:=SumYNxt+disp^[i].Y; SumY2Nxt:=SumY2Nxt+Sqr(disp^[i].Y);
        disp^[j].Y:=ADisp;
        inc(i);inc(j);
      until i>=iblock;
    end;
    npoints:=npoints div 2;
    {Center compuded disperse around corresponding X}
    dec(i);dec(j);
    while i>count-npoints do
    begin
      disp^[i].Y:=disp^[j].Y;
      dec(i);
    end;
    while j>=0 do
    begin
      disp^[i].Y:=disp^[j].Y;
      dec(i);dec(j);
    end;
    while i>=0 do
    begin
      disp^[i].Y:=disp^[i+1].Y;
      dec(i);
    end;

    {Classify points}
    LastX:=disp^[0].X; ib:=0;
    for i:=0 to count-1 do
    begin
      if disp^[i].Y<=BaseMaxDisp then
      begin
        if (disp^[i].X-LastX)>=BaseMinInterval then
        begin
          while ib<=i do
          begin
            isbase^[ib]:=true;
            inc(ib);
          end;
        end;
      end else begin
        LastX:=disp^[i].X;
        while ib<=i do
        begin
          isbase^[ib]:=false;
          inc(ib);
        end;
      end;
    end;
    {Display disperse course for debug purposes}
    if {ulanglob}(UserMode = umSysOp) {v0.12}and false {/v0.12} then {ulantype}
    begin
      {$IFNDEF CONSOLE}
      f := Application.MainForm.ActiveMDIChild;
      if (f <> nil) and (f is TSpectrumForm) then with f as TSpectrumForm do begin
        {v0.11}
        MathCoprMaskUnderflow; {No exception on rounding to zero}
        {/v0.11}
        for i := 0 to count - 1 do begin
          {v0.12}
          if disp^[i].Y < 0 then
            disp^[i].Y := 0
          else
          {/v0.12}
            disp^[i].Y := sqrt(disp^[i].Y) / npoints;
        end;
        AddData(TAcqData.CreateFromPoints(disp, count));
      end;
      {$ENDIF}
    end;
  finally
    if disp <> nil then
      FreeMem(disp,count*sizeof(TExpPoint));
  end;
end;

{ Find index of nearest [X,Y] point for given X }
function XYFindXIndex(points:PExpPoints;count:integer;
                      Xval:TXValue):integer;
var
  ib, ie, i: integer;
begin
  if count<1 then
    begin Result:=-2; exit; end;
  if points^[0].X>Xval then
    begin Result:=-1; exit; end;
  ib:=0;
  ie:=count;
  repeat
    i:=(ib+ie) div 2;
    if Xval<points^[i].X then ie:=i
                         else ib:=i;
  until ib=ie-1;
  Result:=ib;
end;

{ Autodetect baseline }
function TSpectrum.AutoDetectForBase: boolean;
var
  X, X1: TXValue;
  Y: TYValue;
  n: integer;
  ipt, ipt2, cpt: integer;
  newbaseln: TULBRObj;{ in ULBRType.PAS }
  points: TMemoryStream;
  pa: PExpPoints;{ulantype}
  isbase: PBoolArray;
  bmi,bmd: single;

  dname: string;
  fp: TULPRObj;
begin
  Result := false;
  points := nil;
  isbase := nil;
  {v0.11}
  newbaseln := nil;
  cpt := 0;
  {/v0.11}
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    if AcqData.{v0.24}ActiveData.Size{/v0.24 ULAD.Data.Size} = 0 then
      exit;
    {/v0.11}                         {ulbrobju}
    points := TMemoryStream.Create;
    dname := AcqData.ActiveULADrc.ULAD.DataName;{uladtype}
    AcqData.{v0.24}ActiveData{/v0.24 ULAD.Data}.Position := 0;
    points.CopyFrom(AcqData.{v0.24}ActiveData{/v0.24 ULAD.Data}, AcqData.ActiveData.Size);
    cpt := AcqData.ActiveData.Size div sizeof(TExpPoint);
    pa := PExpPoints(points.Memory);
    {v0.25}{/v0.25 BaseLine.Clear;}

    {Find candidates for base}
    GetMem(isbase, cpt * sizeof(boolean));
    {v0.12}
    FillChar(isbase^, cpt * sizeof(boolean),0);
    {/v0.12}
    bmi := AcqData.ULM.BaseMinInterval;
    bmd := AcqData.ULM.BaseMaxDiff;
    XYClassBaseStdDev(pa, cpt, isbase, bmi, bmd);
    {XYClassBase2Der(pa, cpt, isbase, bmi, bmd);}

    {Create base description}
    ipt:=0;
    while ipt<cpt do
    begin
      if isbase^[ipt] then inc(ipt)
      else
      begin
        {v0.11}
        if newbaseln <> nil then begin
          if newbaseln.X2 - newbaseln.X1 < bmi then
            newbaseln.Free;
        end;
        {/v0.11}

        newbaseln := TULBRObj(BaseLine.Add(ULBRID));
        {v0.25}
        newbaseln.DataName := AcqData.ActiveULADRc.ULAD.DataName;
        {/v0.25}
        {Find aproximate left base point}
        ipt2:=ipt; X1:=pa^[ipt2].X;
        repeat
          if ipt2=0 then break;
          if not isbase^[ipt2-1] then break;
          dec(ipt2);
        until (X1-pa^[ipt2].X>bmi/4);
        X:=0; Y:=0; n:=0;
        repeat
          X:=X+pa^[ipt2].X; Y:=Y+pa^[ipt2].Y; inc(n);
          if ipt2=0 then break;
          if not isbase^[ipt2-1] then break;
          dec(ipt2);
        until (X1-pa^[ipt2].X>bmi/2);
        newbaseln.X1:=X/n; newbaseln.Y1:=Y/n;
        {Find right base point}
        repeat
          {v0.28}
          {/v0.28 if ipt>=cpt-1 then break;}
          inc(ipt);
          {v0.28}
          if ipt>=cpt then break;
          {/v0.28}
        until isbase^[ipt];
        ipt2:=ipt-1; X1:=pa^[ipt2].X;
        repeat
          if ipt2>=cpt-1 then break;
          if not isbase^[ipt2+1] then break;
          inc(ipt2);
        until (pa^[ipt2].X-X1>bmi/4);
        X:=0; Y:=0; n:=0;
        repeat
          X:=X+pa^[ipt2].X; Y:=Y+pa^[ipt2].Y; inc(n);
          if ipt2>=cpt-1 then break;
          if not isbase^[ipt2+1] then break;
          inc(ipt2);
        until (pa^[ipt2].X-X1>bmi/2);
        newbaseln.X2 := X/n; newbaseln.Y2 := Y/n;
        newbaseln.X := (newbaseln.X1 + newbaseln.X2)/2;
       {DebLog('NewBaseLn: '+FloatToStr(newbaseln.X1)+','+FloatToStr(newbaseln.y1)
               +' - '+FloatToStr(newbaseln.X2)+','+FloatToStr(newbaseln.Y2));}

        if not PeakFindInPeaksInXRange(AcqData.ULM_ULP, dname, newbaseln.x, newbaseln.x1, newbaseln.x2, fp) then
        begin
          if AcqData.DataIndex <> 0 then begin
            newbaseln.free;
            newbaseln := nil;
          end else begin
            if PeakFindInPeaksInXRange(AcqData.ULM_ULP, '*', newbaseln.x, newbaseln.x1, newbaseln.x2, fp) then
            begin
              newbaseln.free;
              newbaseln := nil;
            end else begin
              if AcqData.ULM.NoUnknownPeaks then
              begin
                newbaseln.free;
                newbaseln := nil;
              end;
            end;
          end;
        end;
      end;
    end;
  finally
    if isbase <> nil then
      FreeMem(isbase,cpt*sizeof(boolean));
    if points <> nil then
      points.Free;
  end;
  Result := true;
end;

function TSpectrum.AutoDetectForPeaks: boolean;
var
  ibl, ipt, ipt2, ipt3, dpt, {cbl,} cpt: integer;
  baseln: TULBRObj; { in ULBRType.PAS }
  points: TMemoryStream;
  pa, tmppa: PExpPoints;{ulantype}
  slope: single;
  MinPeakWidth:single;
  MinPeakHeight:single;
  RejectLevel:single;
  {v0.25}
  noUnknownPeaks: boolean;
  cDataName: string;
  {/v0.25}

 procedure StoreNewPeak(PTX:TXValue);
 var
   pti,i:     integer;
   newpeak:   TULPRObj; { in ULPRType.PAS }
   MinX:      TXValue;
   PHHY,MinY: TYValue;
   PHHHit:    boolean;
   PHHLimX:   TXValue;
   {v0.25}
   fp: TULPRObj;
   {/v0.25}
 begin
   pti:=XYFindXIndex(tmppa, dpt, PTX);
   {Reject small peaks under MinPeakHeight}
   if abs(tmppa^[pti].Y) < MinPeakHeight then
     exit;
   {v0.25 reject peaks that are not in method (if requested)}
   if noUnknownPeaks then begin
     if not PeakFindPeakInMethodByX(PTX, fp) then
       exit; { peak not found in method, exit }

     if fp.DataName <> '' then begin
       { peak is (possibly) on non default data line: }
       if fp.DataName = 'G' then begin
         { it is default in AAA }
         if (cDataName <> '') then begin
           if fp.DataName <> cDataName then
             exit;
         end;
       end else begin
         if cDataName <> fp.DataName then
           exit;
       end;
     end else begin
       { peak is on default data line (has no DataName): }
       if cDataName <> '' then begin
         { possibly nondefault data }
         if cDataName <> 'G' then begin { G is default in AAA }
           exit;
         end;
       end;
     end;
   end;
   {/v0.25}
   {Create new peak object}
   newpeak := TULPRObj(Peaks.Add(ULPRID));
   newpeak.X:= PTX;
   {v0.27}
   newpeak.DataName:=cDataName;
   {/v0.27}
   PHHY:=tmppa^[pti].Y/2;
   { Find Left Peak End }
   PHHLimX:=0;
   i := pti; PHHHit := False; MinY := tmppa^[i].Y; MinX := PTX;
   while i > 0 do
   begin
     dec(i);
     if not PHHHit then
     begin
       if tmppa^[i].Y<=PHHY then
       begin
         PHHLimX:=PTX-(PTX-tmppa^[i].X)*(2+1/4);
         PHHHit:=True;
       end
     end
     else if tmppa^[i].X<PHHLimX then break;
     if tmppa^[i].Y<=MinY then begin
       MinX:=tmppa^[i].X; MinY:=tmppa^[i].Y;
     end
     else if tmppa^[i].X<MinX-2*MinPeakWidth then break;
   end;
   newpeak.X1:=MinX;
   { Find Right Peak End }
   i:=pti; PHHHit:=False; MinY:=tmppa^[i].Y; MinX:=PTX;
   while i<dpt-1 do
   begin
     inc(i);
     if not PHHHit then
     begin
       if tmppa^[i].Y<=PHHY then
       begin
         PHHLimX:=PTX+(tmppa^[i].X-PTX)*(2+1/4);
         PHHHit:=True;
       end;
     end
     else if tmppa^[i].X>PHHLimX then break;
     if tmppa^[i].Y<=MinY then begin
       MinX:=tmppa^[i].X; MinY:=tmppa^[i].Y;
     end
     else if tmppa^[i].X>MinX+2*MinPeakWidth then break;
   end;
   newpeak.X2:=MinX;
 end;

 procedure ProcessOneReg; {imput is tmppa,dpt,MinPeakWidth}
 var
   i : integer;
   PTB,PTE: TXValue;    {Peak Top Begin/End}
   PTX: TXValue;        {Peak Top X Value}
   PTY: TYValue;        {Peak Top Y Value}
   AY:  TYValue;        {Actual Y}
   FindPT: Boolean;     {Look For Peak Top else Valey}
 begin
   i:=0;
   PTB:=tmppa^[i].X; PTE:=PTB;
   PTY:=0;
   FindPT:=True;
   repeat
     AY:=tmppa^[i].Y;
     if FindPT then
     begin
       if AY>PTY then begin PTB:=tmppa^[i].X; PTE:=PTB; PTY:=AY; end
       else if AY=PTY then begin PTE:=tmppa^[i].X; end
       else if tmppa^[i].X>=PTB+MinPeakWidth then
       begin
	 if PTY>=RejectLevel then
         begin
	   PTX:=(PTB+PTE)/2; {Retention Time of New Peak}
	   if (PTX>tmppa^[0].X+MinPeakWidth/3) and
              (PTX<tmppa^[dpt-1].X-MinPeakWidth/3) then
             StoreNewPeak(PTX);
	 end;
	 FindPT:=False;
       end;
     end else begin
       if AY<PTY then begin PTY:=AY; PTE:=tmppa^[i].X; end
       else if tmppa^[i].X>=PTE+MinPeakWidth then FindPT:=True;
     end;
     inc(i);
   until i>=dpt;
 end;

begin
  tmppa :=nil;
  points :=nil;
  {v0.25}
  cDataName := AcqData.ActiveULADRc.ULAD.DataName;
  if AcqData.DataIndex = 0 then begin
    noUnknownPeaks := AcqData.ULM.NoUnknownPeaks;
  end else begin
    noUnknownPeaks:= true;
  end;
  {/v0.25}

  Peaks.DoChangeLock;
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    {/v0.11}
    points := TMemoryStream.Create;
    AcqData.ActiveData.Position := 0;
    points.CopyFrom(AcqData.ActiveData, AcqData.ActiveData.Size);
    cpt := AcqData.ActiveData.Size div sizeof(TExpPoint);
    pa := PExpPoints(points.Memory);
    MinPeakWidth := AcqData.ULM.MinPeakWidth;
    MinPeakHeight := AcqData.ULM.MinPeakHeight;
    {v0.25}{/v0.25 Peaks.Clear;}
    {Detect peaks above found base ranges}
    RejectLevel:=0;
    ibl:=0;
    while ibl < BaseLine.ChildCount do
    begin
      baseln := TULBRObj(BaseLine.Childs[ibl]);
      if (baseln.RecID = ULBRID) {v0.25} and
        (baseln.DataName = AcqData.ActiveULADRc.ULAD.DataName)
      then
      begin
        ipt := XYFindXIndex(pa, cpt, baseln.X1)+1;
        ipt2 := XYFindXIndex(pa, cpt, baseln.X2);
        dpt := ipt2 + 1 - ipt; {Count of points above baseline}
        GetMem(tmppa, dpt * sizeof(TExpPoint));
        Move(pa^[ipt],tmppa^,dpt*sizeof(TExpPoint));
        {Substract baseline from experiment points}
        slope := (baseln.Y2 - baseln.Y1)/(baseln.X2 - baseln.X1);
        for ipt3 := 0 to dpt - 1 do
        begin
          tmppa^[ipt3].Y := tmppa^[ipt3].Y -
             (tmppa^[ipt3].X - baseln.X1) * slope - baseln.Y1;
        end;
        XYFilterGaus(tmppa, dpt, MinPeakWidth/2); {Not needed for filtered data}
        ProcessOneReg;
        FreeMem(tmppa, dpt * sizeof(TExpPoint));
        tmppa:=nil;
      end;
      inc(ibl);
    end;
    {Sort results}
    Peaks.Sort;
    PeaksFindYAndIndexValues;
    PeaksAssignParamsFromMethod;
    RecalculatePeaks;
    Result := false;
  finally
    if tmppa <> nil then
      FreeMem(tmppa);
    points.Free;
    Peaks.DoChangeUnlock;
  end;
end;

procedure TSpectrum.PeakFindYAndIndexValues(p: TULPRObj{v0.26}; ReselectUsrData: boolean{/v0.26});
var
  sp, dp: TExpPoint;
  i: TPointCount;
  y: TYValue;
begin
  sp.X := p.X1;
  {v0.26}
  AcqData.ActiveDataName := p.DataName;
  {/v0.26}
  if AcqData.FindDataPoint(sp, dp, i) then begin
    p.X1 := dp.X;
    p.Y1 := dp.Y;
    p.Index1 := i;
    if FindBaseY(p.DataName, p.X1, y) then
      p.BaseY1 := y
    else
      p.BaseY1 := dp.Y;
  end;
  sp.X := p.X2;
  if AcqData.FindDataPoint(sp, dp, i) then begin
    p.X2 := dp.X;
    p.Y2 := dp.Y;
    p.Index2 := i;
    if FindBaseY(p.DataName, p.X2, y) then
      p.BaseY2 := y
    else
      p.BaseY2 := dp.Y;
  end;
  {v0.26}
  if ReselectUsrData then
    AcqData.UsrDataIndexReselect;
  {/v0.26}
end;

procedure TSpectrum.PeaksFindYAndIndexValues;
var
  i: integer;
  p: TULPRObj;
begin
  Peaks.DoChangeLock;
  try
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then
        PeakFindYAndIndexValues(p{v0.26}, false{/v0.26});
    end;
  finally
    Peaks.DoChangeUnlock;
    {v0.26}
    AcqData.UsrDataIndexReselect;
    {/v0.26}
  end;
end;

function TSpectrum.AutoDetect: boolean;
{v0.25}
var i: integer;
{/v0.25}
begin
  {v0.18}
  {$IFNDEF CONSOLE}
  InfoFormShow(GetTxt({#}'Autodetecting peaks...'));
  {$ENDIF}
  try
    {v0.14}
    if AcqData.ULM.MinPeakWidth = 0 then
      AcqData.ULM.MinPeakWidth := 12;
    if AcqData.ULM.MinPeakHeight = 0 then
      AcqData.ULM.MinPeakHeight := 0.01;
    {/v0.14}
    {v0.25}
    BaseLine.Clear;
    Peaks.Clear;

    {!!!
    if AcqData.DataCount > 1 then begin
      AcqData.ULAD[0].ULAD.Color := clGreen;
      AcqData.ULAD[1].ULAD.Color := clBlue;
    end;}

    for i := 0 to AcqData.ULAD.Count - 1 do
    {/v0.25}
    begin
      {v0.25}
      AcqData.DataIndex := i;
      if i = 1 then begin
        {!!!}
        if AcqData.ActiveULADRc.ULAD.DataName = '' then
          AcqData.ActiveULADRc.ULAD.DataName := 'B';
      end;
      {/v0.25}
      AutoDetectForBase;
      BaseLineUpdateFromMethod;{rrrulbrtype}
      {v0.25}
      {if i > 0 then
        BaseLineRemoveDuplicates;}
      {v0.25}
      AutoDetectForPeaks;
    end;
    {v0.28}
    AcqData.ULA.AutodetectLater := false;
    {/0.28}
    {v0.33}
    Peaks.Sort;
    BaseLine.Sort;
    {/v0.33}
    {v0.31}
    AcqData.UsrDataIndexReselect;
    {/v0.31}
    Result := true;
  finally
    {$IFNDEF CONSOLE}
    InfoFormHide;
    {$ENDIF}
  end;
end;


(*
function TSpectrum.AutoDetect: boolean;
begin
  Result := false;
end;

var
  X: TXValue;
  Y: TYValue;
  ipk, ipt, cpk, cpt: integer;
  peak, newpeak: TULPRObj;{ viz ULPRType.PAS }

  points: TMemoryStream;
  pa: PExpPoints;{ulantype}
begin
  points := TMemoryStream.Create;
  AcqData.ULAD.Data.Position := 0;
  points.CopyFrom(AcqData.ULAD.Data, AcqData.ULAD.Data.Size);
  cpt := AcqData.ULAD.Data.Size div sizeof(TExpPoint);
  ipt := cpt - 1;
  pa := PExpPoints(points.Memory);
  x := pa^[ipt].X;

  newpeak := TULPRObj(Peaks.Add(ULPRID));
  cpk := Peaks.ChildCount;
  ipk := cpk - 1;
  peak := TULPRObj(Peaks.Childs[ipk]);

  Peaks.Sort;
  points.Free;
end;
*)

function TSpectrum.AutoDetectOld: boolean;
var
  APoint: TIndexPoint;
  adp: boolean;
label ex;
begin
  adp := AutoDetectPeaks;
  AutoDetectPeaks := true;
  AutoDetectOld := false;
  if AcqData.GetPointCount < 3 then
    goto ex;
  ClearProcPeak;
  AcqData.SeekPoint(0);
  with ProcPeak do begin
    if not AcqData.ReadIndexPoint(Point_2) then
      goto ex;
    if not AcqData.ReadIndexPoint(Point_1) then
      goto ex;
    if not AcqData.ReadIndexPoint(APoint) then
      goto ex;
    AcqData.SpectrumOpt.BaseLine := Point_2.Point.Y;
    LocMin_1 := Point_2;
    LocMax_1 := Point_2;
    LocMin_2 := Point_2;
    LocMax_2 := Point_2;
    CurMin := Point_2;
    CurMax := Point_2;
    Min_1 := Point_2;
    Max_1 := Point_2;
    Min_2 := Point_2;
    Max_2 := Point_2;
    while AcqData.ReadIndexPoint(APoint) do
      DoPoint(APoint);
    AutoDetectOld := true;
  end;{with ProcPeak}
  PeaksCalculateRatios;
ex:
  AutoDetectPeaks := adp;
end;

procedure TSpectrum.PeaksCalculateRatios;
begin
  if Peaks <> nil then
    CalculatePeaksRatios(Peaks);
end;

function TSpectrum.IsScreenPointInPeakEdge(X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;
var
  sp1, sp2, spp: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
  {v0.09}
  yoffs: TScreenY;
  {/v0.09}
begin
  Result := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.BaseY1;
    ep2.X := p.X2;
    ep2.Y := p.BaseY2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      {v0.09}
      ep1.Y := p.Y1;
      AcqData.ExpToScreen(ep1, AScreenDisp, spp);
      if spp.Y <= sp1.Y then
        yoffs := PointsPeakStickLen
      else
        yoffs := -PointsPeakStickLen;
      {/v0.09}
      if (X >= sp1.X) and (X <= sp1.X + PointsPeakArrowLen) and
         (Y >= sp1.Y + yoffs{PointsPeakStickLen} - PointsPeakArrowLen div 4) and
         (Y <= sp1.Y + yoffs{PointsPeakStickLen} + PointsPeakArrowLen div 4) then
      begin
        Result := true;
        PeakIndex := i;
        exit;
      end;
    end;
    if ons2 = 0 then begin
      {v0.09}
      ep2.Y := p.Y2;
      AcqData.ExpToScreen(ep2, AScreenDisp, spp);
      if spp.Y <= sp2.Y then
        yoffs := PointsPeakStickLen
      else
        yoffs := -PointsPeakStickLen;
      {/v0.09}
      if (X >= sp2.X - PointsPeakArrowLen) and (X <= sp2.X) and
         (Y >= sp2.Y + yoffs{PointsPeakStickLen} - PointsPeakArrowLen div 4) and
         (Y <= sp2.Y + yoffs{PointsPeakStickLen} + PointsPeakArrowLen div 4)
      then begin
        Result := true;
        PeakIndex := i;
        exit;
      end;
    end;
  end;
end;

function TSpectrum.IsScreenXInPeak(X: TScreenX; const AScreenDisp:TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
{  ons1, ons2: integer;}
begin
  Result := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    {ons1 := }AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    {ons2 := }AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if (X >= sp1.X) and (X <= sp2.X) then begin
      Result := true;
      PeakIndex := i;
      exit;
    end;
    {
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X <= (AScreenDisp.Right) then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end else begin
      if (ons1 and opLeft) <> 0 then begin
        if (ons2 = 0) then begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if (ons2 and opRight) <> 0 then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
    }
  end;
end;

function TSpectrum.IsScreenPointInBaseLineEdge(var X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer; var LeftEdge:boolean): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULBRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  Result := false;
  PeakIndex := -1;
  if (BaseLine = nil) then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.RecID <> ULBRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      if (X >= sp1.X - PointsBaseLineCircleRadius) and (X <= sp1.X + PointsBaseLineCircleRadius)
        and (Y >= sp1.Y - PointsBaseLineCircleRadius) and (Y <= sp1.Y + PointsBaseLineCircleRadius)
      then begin
        X := sp1.X;
        Result := true;
        PeakIndex := i;
        {v0.25}
        AcqData.UsrActiveDataName := p.DataName;
        {/v0.25}
        LeftEdge := true;
        exit;
      end;
    end;
    if ons2 = 0 then begin
      if (X >= sp2.X - PointsBaseLineCircleRadius) and (X <= sp2.X + PointsBaseLineCircleRadius)
        and (Y >= sp2.Y - PointsBaseLineCircleRadius) and (Y <= sp2.Y + PointsBaseLineCircleRadius)
      then begin
        Result := true;
        X := sp2.X;
        PeakIndex := i;
        {v0.25}
        AcqData.UsrActiveDataName := p.DataName;
        {/v0.25}
        LeftEdge := false;
        exit;
      end;
    end;
  end;
end;

function TSpectrum.IsScreenXInBaseLine(X: TScreenX; const AScreenDisp:TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULBRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
{  ons1, ons2: integer;}
begin
  Result := false;
  PeakIndex := -1;
  if (BaseLine = nil) then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.RecID <> ULBRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    {ons1 := }AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    {ons2 := }AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if (X >= sp1.X) and (X <= sp2.X) then begin
      Result := true;
      PeakIndex := i;
      exit;
    end;
    {
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X <= (AScreenDisp.Right) then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end else begin
      if (ons1 and opLeft) <> 0 then begin
        if (ons2 = 0) then begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if (ons2 and opRight) <> 0 then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
    }
  end;
end;

function TSpectrum.IsScreenPeakStart(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  IsScreenPeakStart := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X < AScreenDisp.Right{(AScreenDisp.Left + AScreenDisp.Width + sp1.X) div 2} then begin
            IsScreenPeakStart := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X < (sp2.X + sp1.X) div 2 then begin
            IsScreenPeakStart := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
  end;
end;
{
var
  sc: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  pt: TExpPoint;
begin
  IsScreenPeakStart := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
   exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    pt.X := p.X1;
    pt.Y := p.Y1;
    if AcqData.ExpToScreen(pt, AScreenDisp, sc) = 0 then begin
      if abs(sc.X - X) < 2 then begin
        IsScreenPeakStart := true;
        PeakIndex := i;
        exit;
      end;
    end;
  end;
end;
}

{v0.09}
function TSpectrum.ULBRToScreenPeak(BL:TULBRObj; const AScreenDisp: TScreenDisp;
  var SP:TScreenPeak): integer;
var
  ep: TExpPoint;
  ons: integer;
begin
{  Result := -1;
  if BL = nil then
    exit;}
  ep.X := bl.X1;
  ep.Y := bl.Y1;
  ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p1);
  if ((ons and opLeft) <> 0) then
    sp.p1.X := AScreenDisp.Left;
  Result := ons;

  ep.X := bl.X2;
  ep.Y := bl.Y2;
  ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p2);
  if ((ons and opRight) <> 0) then
    sp.P2.X := AScreenDisp.Right;
  Result := Result or ons;
end;
{/v0.09}

procedure TSpectrum.GetScreenBaseLineRec(X: TScreenX; const AScreenDisp: TScreenDisp;
  var CurIndex: integer;
  var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
var
  sp: TScreenPeak;
  {v0.09}{/v0.09
  ep1, ep, ep2: TExpPoint;
  ons1, ons2: integer;{ is on screen? }
  bl: TULBRObj;
  cnt, i: integer;
  {v0.09}
  xp: TScreenPoint;
  xep: TExpPoint; { exp point at X pos }
  mindifx: TXValue;
  difx: TXValue;{ulantype}
  pbl, cbl, nbl: TULBRObj;
  {/0.09}
begin
  FillChar(PrevBL, sizeof(PrevBL), $FF);
  FillChar(CurBL, sizeof(CurBL), $FF);
  FillChar(NextBL, sizeof(NextBL), $FF);
  CurIndex := -1;
  mindifx := MaxXValue;

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextBL := sp;
  end;
  if BaseLine = nil then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;

  {v0.09}
  pbl := nil;
  nbl := nil;
  cbl := nil;

  xp.X := X;
  xp.Y := 0;
  if not AcqData.ScreenToExp(xp, AScreenDisp, xep) then
    exit;
  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    if (bl.X2 < xep.X) then begin
      pbl := bl;
    end else if (bl.X1 > {v0.11} xep.X{/v0.11 X}) then begin
      nbl := bl;
      break;
    end else begin
      difx := xep.X - {v0.11} bl.X1{/v0.11 X};
      if difx < mindifx then begin
        if cbl <> nil then
          pbl := cbl;
        cbl := bl;
        CurIndex := i;
        mindifx := difx;
      end;
    end;
  end;

  if pbl <> nil then ULBRToScreenPeak(pbl, AScreenDisp, PrevBL);
  if cbl <> nil then ULBRToScreenPeak(cbl, AScreenDisp, CurBL);
  if nbl <> nil then ULBRToScreenPeak(nbl, AScreenDisp, NextBL);
  {/0.09
  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    ep1.X := bl.X1;
    ep1.Y := bl.Y1;
    ep.X := bl.X;
    ep.Y := bl.Y;
    ep2.X := bl.X2;
    ep2.Y := bl.Y2;
    FillChar(sp, sizeof(sp), $FF);
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp.p1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp.p2);
    if ons2 = 0 then begin
      if (X > sp.p2.X) then begin
        PrevBL := sp;
      end else begin
        if (ons1 <> 0) or (X >= sp.p1.x) then begin
          if CurIndex < 0 then begin
            CurBL := sp;
            CurIndex := i;
          end else begin
            NextBL := sp;
            break;
          end;
        end;
      end;
    end;
    if ons1 = 0 then begin
      if (X < sp.p1.X) then begin
        NextBL := sp;
        break;
      end else begin
        if (ons2 = 0) then begin
          if X <= sp.P2.X then begin
            if CurIndex < 0 then begin
              CurBL := sp;
              CurIndex := i;
            end else begin
              if (sp.X1 <> CurBL.X1) or (sp.X2 <> CurBL.X2) then begin
                NextBL := sp;
                break;
              end;
            end;
          end;
        end else begin
          CurBL := sp;
          CurIndex := i;
        end;
      end;
    end;

    if ((ons1 and opLeft) <> 0) and ((ons2 and opRight) <> 0) then begin
      CurBL.P1.X := AScreenDisp.Left;
      CurBL.P2.X := AScreenDisp.Right;
      CurIndex := i;
      break;
    end;

  end;
  }
end;

{v0.09}
procedure TSpectrum.GetScreenBaseLineNeighbours(Index:integer; const AScreenDisp: TScreenDisp;
  var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
  { Returns inc CurBL screen coordinates of Index baseline section, plus
    in PrevBL and NextBL coordinates of sections on the left and on the right }
var
  sp: TScreenPeak;
  bl: TULBRObj;
  cnt, i: integer;
  pbl, cbl, nbl: TULBRObj;
begin
  FillChar(PrevBL, sizeof(PrevBL), $FF);
  FillChar(CurBL, sizeof(CurBL), $FF);
  FillChar(NextBL, sizeof(NextBL), $FF);

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextBL := sp;
  end;
  if BaseLine = nil then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  pbl := nil;
  nbl := nil;
  cbl := nil;

  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    if i < Index then
      pbl := bl
    else if i = Index then
      cbl := bl
    else begin
      nbl := bl;
      break;
    end;
  end;
  if pbl <> nil then ULBRToScreenPeak(pbl, AScreenDisp, PrevBL);
  if cbl <> nil then ULBRToScreenPeak(cbl, AScreenDisp, CurBL);
  if nbl <> nil then ULBRToScreenPeak(nbl, AScreenDisp, NextBL);
end;
{/v0.09}

procedure TSpectrum.GetScreenPeaks(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PrevPeak: TScreenPeak; var CurPeak: TScreenPeak; var NextPeak: TScreenPeak);
var
  sp: TScreenPeak;
  ep1, ep, ep2: TExpPoint;
  ons1, {ons, }ons2: integer;{ is on screen? }
  p: TULPRObj;
  cnt, i: integer;
begin
  FillChar(PrevPeak, sizeof(PrevPeak), $FF);
  FillChar(CurPeak, sizeof(CurPeak), $FF);
  FillChar(NextPeak, sizeof(NextPeak), $FF);

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextPeak := sp;
  end;
  if Peaks = nil then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;

  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep.X := p.X;
    ep.Y := p.Height;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    FillChar(sp, sizeof(sp), $FF);
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp.p1);
    {ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p);}
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp.p2);
    if ons2 = 0 then begin
      if (X > sp.p2.X) then begin
        PrevPeak := sp;
      end else begin
        if (ons1 <> 0) or (X >= sp.p1.x) then begin
          CurPeak := sp;
          {v0.25}
          AcqData.UsrActiveDataName := p.DataName;
          {/v0.25}
        end;
      end;
    end;
    if ons1 = 0 then begin

      if (X < sp.p1.X) then begin
        NextPeak := sp;
        break;
      end else begin
        if (ons2 = 0) then begin
          if X <= sp.P2.X then begin
            CurPeak := sp;
            {v0.25}
            AcqData.UsrActiveDataName := p.DataName;
            {/v0.25}
          end;
        end else begin
          CurPeak := sp;
          {v0.25}
          AcqData.UsrActiveDataName := p.DataName;
          {/v0.25}
        end;
      end;
    end;

    if ((ons1 and opLeft) <> 0) and ((ons2 and opRight) <> 0) then begin
      CurPeak.P1.X := AScreenDisp.Left;
      CurPeak.P2.X := AScreenDisp.Right;{AScreenDisp.Left + AScreenDisp.Width;}
      break;
    end;

  end;
end;


function TSpectrum.IsScreenPeakStop(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  IsScreenPeakStop := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons2 = 0 then begin
      if (X <= sp2.X) then begin
        if (ons1 <> 0) or (X >= ((sp2.X + sp1.X) div 2)) then begin
          IsScreenPeakStop := true;
          PeakIndex := i;
          exit;
        end;
      end;
    end;
  end;
end;

function TSpectrum.MoveScreenPeak(OldStartX, NewStartX: TScreenX;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULPRObj;
  d: TXValue;
  op1,op2,
  np1,np2: TExpPoint;
  index1,index2: TPointCount;
begin
  MoveScreenPeak := false;
  if not IsScreenPeakStart(OldStartX, AScreenDisp, pi) then
    exit;
  sp.X := NewStartX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  p := TULPRObj(Peaks.Childs[pi]);
  d := ap.X - p.X1;
  op1.X := p.X1 + d;;
  op1.Y := p.Y1;
  op2.X := p.X2 + d;
  op2.Y := p.Y2;
  if not AcqData.FindDataPoint(op1, np1, index1) then
    exit;
  if not AcqData.FindDataPoint(op2, np2, index2) then
    exit;
  p.X1 := np1.X;
  p.Y1 := np1.Y;
  p.Index1 := index1;
  p.X2 := np2.X;
  p.Y2 := np2.Y;
  p.Index2 := index2;
  DoPeakLimitsChanged(p);
  {
  p^.P1.Point := np1;
  p^.P1.Index := index1;
  p^.P2.Point := np2;
  p^.P2.Index := index2;
  }
  MoveScreenPeak := true;
end;

function TSpectrum.SizeScreenPeak(OldStopX, NewStopX: TScreenX;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer; Right:boolean): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULPRObj;
  np: TExpPoint;
  index: TPointCount;
begin
  SizeScreenPeak := false;
  if not IsScreenXInPeak(OldStopX, AScreenDisp, pi) then
    exit;
  {
  if not IsScreenPeakStop(OldStopX, AScreenDisp, pi) then
    exit;
  }
  sp.X := NewStopX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULPRObj(Peaks.Childs[pi]);{PPeak(ListAt(Peaks,pi));}
  p.DoChangeLock;
  try
    if right then begin
      if (p.Index1 >= index) or (p.X1 >= np.X) then begin
        {if p.CanDestroy then
          p.Free;}
        exit;
      end;
      p.X2 := np.X;
      p.Y2 := np.Y;
      p.Index2 := index;
    end else begin
      if (p.Index2 <= index) or (p.X2 <= np.X) then begin
        {if p.CanDestroy then
          p.Free;}
        exit;
      end;
      p.X1 := np.X;
      p.Y1 := np.Y;
      p.Index1 := index;
    end;
  {  p^.P2.Point := np;
    p^.P2.Index := index;}
    DoPeakLimitsChanged(p);
    SizeScreenPeak := true;
  finally
    p.DoChangeUnlock;
  end;
end;

procedure TSpectrum.ToggleSelectScreenPeak(X: TScreenX;
  const AScreenDisp: TScreenDisp; Shift: TShiftState);
var
  index: integer;
{  p, o: TULPRObj;
  i, j, index, cc: integer;
  otherSelIndex: integer;}
begin
  if IsScreenXInPeak(X, AScreenDisp, index) then begin
    Peaks.ToggleSelectChild(index, Shift);
(*
    p := TULPRObj(Peaks.Childs[index]);
    cc := Peaks.ChildCount;
    if (ssShift in Shift) then begin

      otherSelIndex := -1;
      for i := 0 to cc - 1 do begin
        if i <> index then begin
          o := TULPRObj(Peaks.Childs[i]);
          if o.IsFlagSet(rfSelected) then begin
            otherSelIndex := i;
            break;
          end;
        end;
      end;
      if otherSelIndex <> -1 then begin
        if otherSelIndex < index then begin
          i := otherSelIndex;
        end else begin
          i := index;
          index := otherSelIndex;
        end;
        for j := 0 to i - 1 do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, false);
        end;
        for j := i to index do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, true);
        end;
        for j := index + 1 to cc - 1 do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, false);
        end;

      end else begin
        p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));
      end;
    end else if (ssCtrl in Shift) then begin

      p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));

    end else begin

      for i := 0 to Peaks.ChildCount - 1 do begin
        o := TULPRObj(Peaks.Childs[i]);
        if i <> index then
          o.SetFlag(rfSelected, false);
      end;
      p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));

    end;
*)
  end;
end;

procedure TSpectrum.ToggleSelectScreenBaseLine(X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; Shift: TShiftState);
var
  index: integer;
  leftedge:boolean;
  {
  p, o: TULPRObj;
  i, j, index, cc: integer;
  otherSelIndex: integer;}
begin
  if IsScreenPointInBaseLineEdge(X, Y, AScreenDisp, index, leftedge) then begin
    BaseLine.ToggleSelectChild(index, Shift);
  end;
end;

procedure TSpectrum.DoPeakLimitsChanged(p: TULPRObj);
{var
  y: TYValue;}
begin
  PeakFindYAndIndexValues(p{v0.26}, true{/v0.26});
  {v0.12}
  {/v0.12
  PeakAssignParamsFromMethod(p);}
{  if FindBaseY(p.X1, y) then
    p.BaseY1 := y;
  if FindBaseY(p.X2, y) then
    p.BaseY2 := y;
}
end;

function TSpectrum.FindBaseY(const ADataName: string; X: TXValue; var Y: TYValue):boolean;
var
  b: TULBRObj;
  i, cc: integer;
  maxy: TYValue;
  cy: TYValue;
  fnd:boolean;
begin
  FindBaseY := false;
  maxy := MinYValue;
  fnd := false;
  cc := AcqData.ULB.ChildCount;
  if cc > 0 then begin
    for i := 0 to cc - 1 do begin
      b := TULBRObj(AcqData.ULB.Childs[i]);
      if b.RecID <> ULBRID then
        continue;
      if (ADataName<>'*') and (ADataName<>b.DataName) then
        continue;
      if (b.X1 <= X) and (X <= b.X2) then begin
        cy := CountYOnLine(X, b.X1, b.Y1, b.X2, b.Y2);
        if (cy > maxy) then begin
          maxy := cy;
          fnd := true;
        end;
      end;
    end;
    if fnd then begin
      y := maxy;
      FindBaseY := true;
    end;
  end;
end;

procedure TSpectrum.CopySelectedPeaksToMethod;
var
  p, mp: TULPRObj;
  i: integer;
begin
  for i := 0 to AcqData.ULP.ChildCount - 1 do begin
    p := TULPRObj(AcqData.ULP.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    if p.IsFlagSet(rfSelected) then begin
      {v0.10}
      if PeakFindPeakInMethod(p, mp) then begin
        mp.Free;
      end;
      {/v0.10}
      mp := TULPRObj(AcqData.ULM_ULP.Add(ULPRID));
      mp.Assign(p);
    end;
  end;
end;

procedure TSpectrum.CopySelectedBaseLineToMethod;
var
  b, mb: TULBRObj;
  i: integer;
begin
  for i := 0 to AcqData.ULB.ChildCount - 1 do begin
    b := TULBRObj(AcqData.ULB.Childs[i]);
    if b.RecID <> ULBRID then
      continue;
    if b.IsFlagSet(rfSelected) then begin
      mb := TULBRObj(AcqData.ULM_ULB.Add(ULBRID));
      mb.Assign(b);
    end;
  end;
end;

procedure TSpectrum.PeaksAssignParamsFromMethod;
var
  i: integer;
begin
  AcqData.ULP.DoChangeLock;
  try
    for i := 0 to AcqData.ULP.ChildCount - 1 do begin
      PeakAssignParamsFromMethod(TULPRObj(AcqData.ULP.Childs[i]));
    end;
  finally
    AcqData.ULP.DoChangeUnlock;
  end;
end;

{v0.11}
procedure TSpectrum.PeakAssignPeakCharacterization(Dest, Src: TULPRObj);
begin
  Dest.PeakName := Src.PeakName;
  Dest.GroupName := Src.GroupName;
  Dest.Response := Src.Response;
  Dest.InternalStandard := Src.InternalStandard;
  Dest.Window := Src.Window;
  {v0.12}
  Dest.UsrPeakCoef := Src.UsrPeakCoef;
  Dest.Amount := Src.Amount;
  {/v0.12}
  {v0.25}
  Dest.DataName := Src.DataName;
  {/v0.25}
end;
{/v0.11}

procedure TSpectrum.PeakAssignParamsFromMethod(p: TULPRObj);
var mp: TULPRObj;
begin
  if PeakFindPeakInMethod(p, mp) then begin
    {v0.11}
    PeakAssignPeakCharacterization(p, mp);
    {/v0.11
    p.PeakName := mp.PeakName;
    p.GroupName := mp.GroupName;}
  end;
end;

{v0.25}
function TSpectrum.PeakFindInPeaksByX(pl: TULPObj; x: TXValue; var fp: TULPRObj): boolean;
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
  w: TXValue;
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    curdif := abs(X - curmp.X);
    w := curmp.Window;
    if w = 0 then begin
      w := AcqData.ULM.MinPeakWidth;
    end;
    if curdif <= (w / 2) then begin
      if curdif < mindif then begin
        mindif := curdif;
        minmp := curmp;
      end;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

function TSpectrum.PeakFindInPeaksInXRange(pl: TULPObj; const ADataName: string; x, xmin, xmax: TXValue; var fp: TULPRObj): boolean;
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
{  w: TXValue;}
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    if (ADataName <> '*') then begin
      if curmp.DataName <> ADataName then
        continue;
    end;
    if curmp.RecID <> ULPRID then
      continue;
    if curmp.x > xmax then
      continue;
    if curmp.x < xmin then
      continue;
    curdif := abs(X - curmp.X);
    if curdif < mindif then begin
      mindif := curdif;
      minmp := curmp;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

{/v0.25}

{v0.11}
function TSpectrum.PeakFindInPeaks(pl: TULPObj; p: TULPRObj; var fp: TULPRObj): boolean;
  { find in pl peaks the peak fp, that has p.X in fp's window interval }
{/v0.11
function TSpectrum.PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean; }
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
  {v0.11}
  w: TXValue;
  {/v0.11}
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to {v0.11}pl{/v0.11 AcqData.ULM_ULP}.ChildCount - 1 do begin
    curmp := TULPRObj({v0.11}pl{/v0.11 AcqData.ULM_ULP}.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    curdif := abs(p.X - curmp.X);
    {v0.11}
    w := curmp.Window;
    if w = 0 then begin
      w := AcqData.ULM.MinPeakWidth;
    end;
    {/v0.11}
    if curdif <= ({v0.11}w{/v0.11 curmp.Window} / 2) then begin
      if curdif < mindif then begin
        mindif := curdif;
        minmp := curmp;
      end;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

{v0.12}
function TSpectrum.FindPeakByName(pl: TULPObj; const AName: TPeakName; var fp: TULPRObj): boolean;
 { find in pl peaks the peak fp, that has p.Name = AName }
var
  i: integer;
  curmp: TULPRObj;
begin
  Result := false;
  fp := nil;
  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    if AName = curmp.PeakName then
    begin
      Result := true;
      fp:=curmp;
      break;
    end;
  end;
end;
{/v0.12}

{v0.25}
function TSpectrum.PeakFindPeakInMethodByX(x: TXValue; var mp: TULPRObj):boolean;
begin
  Result := PeakFindInPeaksByX(AcqData.ULM_ULP, x, mp);
end;
{/v0.25}
{v0.11}
function TSpectrum.PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;
begin
  Result := PeakFindInPeaks(AcqData.ULM_ULP, p, mp);
end;
{/v0.11}

{v0.25}
procedure TSpectrum.BaseLineRemoveDuplicates;
var
  b, b1, b2: TULBRObj;
  i: integer;
begin
  b1 := nil;
  i := 0;
  while i < BaseLine.ChildCount do begin
    b := TULBRObj(BaseLine.Childs[i]);
    if b.RecID <> ULBRID then begin
      inc(i);
      continue;
    end;
    if b1 = nil then begin
      b1 := b
    end else begin
      b2 := b;
      if b1.X2 > b2.X1 then begin
        b2.Free;
        continue;
      end else
        b1 := b2;
    end;
    inc(i);
  end;
end;
{/v0.25}

procedure TSpectrum.BaseLineUpdateFromMethod;
var
  i, mi: integer;
  b, mb, nb: TULBRObj;
  brModified, bModified: boolean;

begin
  i := 0;
  bModified := false;
  BaseLine.DoChangeLock;
  try
    while i < BaseLine.ChildCount do begin
      b := TULBRObj(BaseLine.Childs[i]);
      if b.RecID <> ULBRID then begin
        inc(i);
        continue;
      end;

      brModified := false;
      for mi := 0 to AcqData.ULM_ULB.ChildCount - 1 do begin
        mb := TULBRObj(AcqData.ULM_ULB.Childs[mi]);
        if mb.RecID <> ULBRID then
          continue;
        if (b.X2 > mb.X1) and (b.X1 < mb.X2) then begin
          { some overlap of baseline record and method baseline record detected }

          brModified := true;
          bModified := true;
          if b.X1 < mb.X1 then begin
            if b.X2 <= mb.X2 then begin
              b.X2 := mb.X1;
            end else begin
              nb := TULBRObj(BaseLine.Add(ULBRID));
              nb.X1 := mb.X2;
              nb.X2 := b.X2;
              b.X2 := mb.X1;
            end;
          end else begin
            if b.X2 <= mb.X2 then begin
              if b.CanDestroy then begin
                b.Free;
                b := nil;
                break;
              end;
            end else begin
              b.X1 := mb.X2;
            end;
          end;
        end;
      end;
      if (b <> nil) then begin
        if brModified then begin
          DoBaseLineLimitsChanged(b);
          {UpdateBaseLineRecY(b);}
        end;
        inc(i);
      end;
    end;{/while}
    if bModified then
      BaseLine.Sort;
  finally
    BaseLine.DoChangeUnlock;
  end;
end;

destructor TSpectrum.Destroy;
begin
  {PeaksClear;}
  if AcqData <> nil then begin
    AcqData.Spectrum := nil;
  end;
  inherited Destroy;
end;

{v0.11}
function TSpectrum.PeaksResponsesCalculate: integer;
var
  i: integer;
  p: TULPRObj;
  zeroamount:integer;
  f: TFactor;
begin
  ClearResult;
  Result := 0;

  zeroamount := 0;
  {v0.28}
  f := CombineFactors(AcqData.ULM.Factor, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
  {/v0.28}

  for i := 0 to Peaks.ChildCount - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    if p.Amount <> 0 then begin
      if p.UsrPeakCoef = 0 then
        p.UsrPeakCoef := 1;
      p.Response := (p.AreaSize / p.Amount) * p.UsrPeakCoef * f;
    end else begin
      p.Response := 0;
      inc(zeroamount);
    end;
  end;
  if zeroamount > 0 then begin
    {v0.25 not an error, if Amount set 0, then the peak should be ignored}
    {/v0.25
    SetResult(srPeakAmountZero, 'In peaks response calculate');}
  end;
end;

function TSpectrum.PeaksAmountsCalculate: integer;
var
  r: integer;
begin
  Result := 0;
  {r := 0;}
  if AcqData.ULM.Factor=0 then
    AcqData.ULM.Factor:=1; {Help for dummies}
  if AcqData.ULM.UseCalibrationFile then begin
    r := PeaksAmountsCalculateCalibrationFile;
    if Result = 0 then
      Result := r;
  end;{ulprtype ulatype}
  if AcqData.ULM.UseInternalStandard then begin
    r := PeaksAmountsCalculateInternalStandard;
    if Result = 0 then
      Result := r;
  end;
  r := PeaksAmountsCalculateValues;
  if Result = 0 then
    Result := r;
end;

{v0.13}
function TSpectrum.PeaksAutoCalculate: integer;
begin
  if AcqData.ULA.CalibrationStandard then
    Result := PeaksResponsesCalculate
  else
    Result := PeaksAmountsCalculate;
end;
{/v0.13}

function TSpectrum.PeaksAmountsCalculateCalibrationFile: integer;
var
  d: TAcqData;
  i: integer;
  p, cp: TULPRObj;
  {v0.12}
  f: TFactor;
  a: integer;
  {/v0.12}
begin
  Result := 0;
  {v0.12}
  if AcqData.Cal_ULP <> nil then begin
    if AcqData.ULM.CalibrationFileName <> '' then begin
      a := FileAge(AcqData.ULM.CalibrationFileName);
      if (a <> -1) and (a <> AcqData.ULM.CalibrationFileAge) then
      begin
        AcqData.Cal_ULP.Free;
        AcqData.Cal_ULP := nil;
      end;
    end;
  end;
  {/v0.12}
  if AcqData.Cal_ULP = nil then begin
    if AcqData.ULM.CalibrationFileName = '' then begin
      SetResult(srCalibrationFileNotSpecified, '');
      exit;
    end else begin
      AcqData.Cal_ULP := TULPObj.Create(AcqData.ULA);
      AcqData.Cal_ULP.SetFlag(rfTemporary, true);
      d := TAcqData.Create(AcqData.ULM.CalibrationFileName, omRead);
      try
        AcqData.Cal_ULP.Assign(d.ULP);
        {v0.12}
        {v0.28}
        f := CombineFactors(d.ULM.Factor, d.ULA.MultiplyFactor, d.ULA.DivideFactor);
        {/v0.28
        f := d.ULM.Factor;}
        {/v0.12}
        {v0.13}
        { Warning f is not persistent between calls }
        {v0.28}{/v0.28
        if f=0 then
          f:=1;}
        {/v0.13}
      finally
        {v0.30}
        d.ULF.Modified := false;
        {/v0.30}
        d.Free;
      end;
      {v0.12}
      AcqData.ULM.CalibrationFileAge := FileAge(AcqData.ULM.CalibrationFileName);
      {/v0.12}
      {v0.13}
      for i:= 0 to AcqData.Cal_ULP.ChildCount - 1 do begin
        cp := TULPRObj(AcqData.Cal_ULP.Childs[i]);
        if cp.RecID <> ULPRID then
          continue;
        if cp.UsrPeakCoef = 0 then
          cp.UsrPeakCoef := 1;
        if (cp.AreaSize <> 0) and (cp.Amount <> 0) then
          cp.Response := cp.AreaSize / cp.Amount * cp.UsrPeakCoef * f
        else
          cp.Response := 0;
      end;
      {/v0.13}
    end;
  end;
  {v0.31}
  Peaks.DoChangeLock;
  try
  {/v0.31}
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      if {v0.12} FindPeakByName(AcqData.Cal_ULP, p.PeakName, cp)
         {/v0.12 PeakFindInPeaks(AcqData.Cal_ULP, p, cp)} then
      {v0.13}
      begin
        p.Response := cp.Response;
      end else begin
        p.Response := 0;
      end;
    end;
  {v0.31}
  finally
    Peaks.DoChangeUnlock;
  end;
  {/v0.31}
end;

{v0.12}
function TSpectrum.PeaksAmountsCalculateInternalStandard: integer;
{/v0.12
function TSpectrum.PeaksAmountsCalculateFromInternalStandard: integer;}
var
  istd: integer;
  i: integer;
  p: TULPRObj;
begin
  ClearResult;
  Result := 0;
  {Pro prepocet analyzy na interni standard
    is=find Peak[i].IntStandard
    Method.Factor=Peak[is].Amount/
		((Peak[is].Area/Peak[is].Response)*Peak[is].UserCoef); }
  {v0.31}
  Peaks.DoChangeLock;
  try
  {/v0.31}
    istd := -1;
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      if p.InternalStandard then begin
        istd := i;
        if p.Amount = 0 then begin
          if AcqData.ULA.ISTDAmount = 0 then begin
            SetResult(srPeakAmountZero, p.PeakName + ' - internal standard');
            exit;{ulatype}
          end else begin
            p.Amount := AcqData.ULA.ISTDAmount;
          end;
        end;
        if p.Response = 0 then begin
          SetResult(srPeakResponseZero, p.PeakName + ' - internal standard');
          exit;
        end;
        if p.AreaSize = 0 then begin
          SetResult(srPeakAreaZero, p.PeakName + ' - internal standard');
          exit;
        end;
        if p.UsrPeakCoef = 0 then begin
          p.UsrPeakCoef := 1;
        end;
        AcqData.ULM.Factor := p.Amount /( (p.AreaSize / p.Response) * p.UsrPeakCoef )
           {v0.28} / CombineFactors(1, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
           {/v0.28};
        break;
      end;
    end;
    if istd = -1 then begin
      SetResult(srNoInternalStandardSpecified,' - in calculate amounts');
      exit;
    end;
  {v0.31}
  finally
    Peaks.DoChangeUnlock;
  end;
  {/v0.31}
end;

{v0.12}
function TSpectrum.PeaksAmountsCalculateValues: integer;
var
  i: integer;
  p: TULPRObj;
  {v0.28}
  f: TFactor;
  {/v0.28}
begin
{ Vypocet standardni analyzy
    with Peak[i]
      if not Peak[i].IntStandard then
        Amount=(Area/Response)*UserCoef*Method.Factor; }
  Result := 0;

  {v0.31}
  Peaks.DoChangeLock;
  try
  {/v0.31}

    {v0.28}
    f := CombineFactors(AcqData.ULM.Factor, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
    {/v0.28}

    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      if not p.InternalStandard then begin
        if p.UsrPeakCoef = 0 then
          p.UsrPeakCoef := 1;
        {if p.AreaSize = 0 then begin
          SetResult(srPeakAreaZero, p.PeakName + ' - calculate amount');
          exit;
        end;}
        if p.Response = 0 then begin
          {SetResult(srPeakResponseZero, p.PeakName + ' - calculate amount');}
          p.Amount := 0;
        end else begin
          p.Amount := (p.AreaSize / p.Response) * p.UsrPeakCoef * {v0.28}f{/v0.28 AcqData.ULM.Factor};
        end;
      end;
    end;
  {v0.31}
  finally
    Peaks.DoChangeUnlock;
  end;
  {/v0.31}
end;
{/v0.12}

procedure TSpectrum.ClearResult;
begin
  LastResult := 0;
end;

procedure TSpectrum.SetResult(sr: TSpectrumResult; const msg:string);
var
  s: string;
begin
  LastResult := sr;
  case sr of
    srPeakResponseZero : s := 'Peak Response Zero';
    srPeakAmountZero : s := 'Peak Amount Zero';
    srPeakAreaZero : s := 'Peak Area Zero';
    srNoInternalStandardSpecified : s := 'No Internal Standard Specified';
    srCalibrationFileNotSpecified : s := 'Calibration File Not Specified';
  else
    s := IntToStr(sr);
  end;
  raise ESpectrum.Create(GetTxt({#}'Data Error') + ': ' + s + ' ' + msg);
end;
{/v0.11}

{/TSpectrum}

{TULADRc}
function TULADRc.GetData: TStream;
begin
  Result := ULAD.Data;
end;

{v0.31}
constructor TULADRc.Create;
begin
  inherited;
  PointTimeInterval := UlanPointTimeInterval;
  YCoef := 1;
end;
{/v0.31}
{/TULADRc}

{TULADs}
procedure TULADs.CurDataSizeUpdate;
var
  i: integer;
  r: TULADRc;
begin
  for i := 0 to Count - 1 do begin
    r := ULAD[i];
    r.CurDataSize := r.Data.Size;
    {v0.31}
    r.CurDataPos := r.Data.Position;
    {/v0.31}
  end;
end;

procedure TULADs.SetZeroSizes;
var
  ud: TULADRc;
  i: integer;
begin
  for i := 0 to Count -1 do begin
    ud := GetULADRc(i);
    ud.Data.Size := 0;
    {v0.31}
    ud.CurDataSize := 0;
    ud.CurDataPos := 0;
    {/v0.31}
  end;
end;

{v0.31}
procedure TULADs.Start;
var i: integer;
begin
  for i := 0 to Count - 1 do
    ULAD[i].CurTime := 0;
end;

function TULADs.FindULAD(AULAD: TULADObj; var Index: integer): boolean;
var i: integer;
begin
  Result := false;
  for i := 0 to Count - 1 do begin
    if ULAD[i].ULAD = AULAD then begin
      Result := true;
      Index := i;
      exit;
    end;
  end;
end;
{/v0.31}

procedure TULADs.Reset;
var
  ud: TULADRc;
  i: integer;
begin
  for i := 0 to Count -1 do begin
    ud := GetULADRc(i);
    ud.Data.Position := 0;
    {v0.31}
    ud.CurDataPos := 0;
    {/v0.31}
  end;
end;

procedure TULADs.Clear;
begin
  while Count > 0 do
    DelULAD(TULADRc(Items[0]).ULAD);
  inherited;
end;

function TULADs.GetULADRc(Index: integer):TULADRc;
begin
  Result := nil;
  if (Index >= 0) and (Index < Count) then begin
    Result := TULADRc(Items[Index]);
  end;
end;

procedure TULADs.Update;
var
  d: TULADObj;
  i, j: integer;
  fnd: boolean;
begin
  {v0.31}
  for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
    d := TULADObj(FAcqData.ULA.Childs[i]);
    if d.RecID = ULADID then begin
      if not FindULAD(d, j) then begin
        AddULAD(d);
      end;
    end;
  end;

  for j := 0 to Count - 1 do begin
    d := ULAD[j].ULAD;
    fnd := false;
    for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
      if d = FAcqData.ULA.Childs[i] then begin
        fnd := true;
        break;
      end;
    end;
    if not fnd then
      DelULAD(d);
  end;
  {/v0.31
  Clear;
  d0 := TULADObj(FAcqData.ULA.FindOrAdd(ULADID, ''));
  AddULAD(d0);
  for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
    o := FAcqData.ULA.Childs[i];
    if (o is TULADObj) and (o <> d0) then begin
      AddULAD(TULADObj(o));
    end;
  end;   }
end;

procedure TULADs.AddULAD(AULAD: TULADObj);
var rc: TULADRc;
begin
  rc := TULADRc.Create;
  rc.ULAD := AULAD;
  AULAD.SetFlag(rfCantDelete, true);
  Add(rc);
end;

procedure TULADs.DelULAD(AULAD: TULADObj);
var
  i: integer;
  ur: TULADRc;
begin
  AULAD.SetFlag(rfCantDelete, false);
  for i := 0 to Count - 1 do begin
    ur := TULADRc(Items[i]);
    if ur.ULAD = AULAD then begin
      Remove(ur);
      exit;
    end;
  end;
end;

constructor TULADs.Create(AAcqData: TAcqDAta);
begin
  inherited Create;
  FAcqData := AAcqData;
  Update;
end;

destructor TULADs.Destroy;
begin
  Clear;
  inherited;
end;

{/TULADs}
{******************** TAcqData *********************}

{TAcqData}
constructor TAcqData.Create(const AFileName: string; AMode: TOpenMode);
begin
  inherited Create;
  {v0.20}{/v0.20 BufPointsClear;}
  SpectrumOpt := DefSpectrumOpt;
  {v0.21}
  DoMath(moNoneOp,0);
  {/v0.21
  FMultiplyX := 1;
  FMultiplyY := 1;}

  ClearScanInfo;
  {v0.14}
  if AMode = omCreateTemplate then begin
    CreateTemplate(AFileName);
  end else
  {/v0.14}
  begin
    SetFileName(AFileName, AMode);
  end;
  {v0.45}
  FCreatedOK := true;
  {/v0.45}
end;

{v0.05}
(*
procedure TAcqData.HeadInit(var AHead:TUldRec);
begin
  with AHead do begin
    Head.RecID := UldID;
    Head.RecLen := sizeof(AHead);
    Info.CreateTime := Now;
    Info.ChangeTime := Now;
    {Desc:TUldDesc;}
    {InstrumentFileName:TInstrumentFileName;}
    {MethodFileName:TMethodFileName;}
  end;
end;
*)
{/v0.05}

{v0.09}
constructor TAcqData.CreateFromPoints(APoints: PExpPoints; APointCount: integer);
var
  i: integer;
  ud: TULADRc;
begin
  inherited Create;
  {v0.20}{/v0.20 BufPointsClear;}
  SpectrumOpt := DefSpectrumOpt;
  {v0.21}
  DoMath(moNoneOp,0);
  {/v0.21}
  ClearScanInfo;
  FMode := omRead;
  ULF := TULFObj.Create(nil);
  CheckULRecords; {ulobju}
  ud := ULAD[ULADIndex];
  ud.ULAD.Data := TMemoryStream.Create;
  TMemoryStream(ud.Data).SetSize(APointCount * sizeof(TExpPoint));
  ud.Data.Position := 0;
  for i := 0 to APointCount - 1 do WritePoint(APoints^[i]);
  ScanData;
end;

procedure TAcqData.CheckULRecords;
begin
  ULA := TULAObj(ULF.FindOrAdd(ULAID, ''));
    ULA.SetFlag(rfCantDelete, true);
  {v0.14}
  ULA.MainUser := Self;
  {/v0.14}
  {v0.24}
  if ULAD = nil then
    ULAD := TULADs.Create(Self)
  else
    ULAD.Update;
  {/v0.24
  ULAD := TULADObj(ULA.FindOrAdd(ULADID, ''));
    ULAD.SetFlag(rfCantDelete, true);}
  ULAF := TULAFObj(ULA.FindOrAdd(ULAFID, ''));
    ULAF.SetFlag(rfCantDelete, true);
  ULM := TULMObj(ULA.FindOrAdd(ULMID, ''));
    ULM.SetFlag(rfCantDelete, true);
  {v0.11}
  CheckULMRecords;
  {/v0.11
  ULMF := TULMFObj(ULM.FindOrAdd(ULMFID, ''));
    ULMF.SetFlag(rfCantDelete, true);
  ULM_ULP := TULPObj(ULM.FindOrAdd(ULPID, ''));
    ULM_ULP.SetFlag(rfCantDelete, true);
  ULM_ULB := TULBObj(ULM.FindOrAdd(ULBID, ''));
    ULM_ULB.SetFlag(rfCantDelete, true);}

  {v0.13}
  ULI := TULIObj(ULA.FindOrAdd(ULIID, ''));
    ULI.SetFlag(rfCantDelete, true);
  {/v0.13}
  {v0.14}
  {$IFNDEF CONSOLE}
  ULI.FindField(fnChannelName).FldDesc.ValuesSource := Channels.ULL;
  if ULI.ChannelName = '' then
    ULI.ChannelName := Channels.ULL.ChannelName;{set to default channel name}
  {$ENDIF}
  {/v0.14}

  ULB := TULBObj(ULA.FindOrAdd(ULBID, ''));
    ULB.SetFlag(rfCantDelete, true);
  ULP := TULPObj(ULA.FindOrAdd(ULPID, ''));
    ULP.SetFlag(rfCantDelete, true);
  ULV := TULVObj(ULA.FindOrAdd(ULVID, ''));
    ULV.SetFlag(rfCantDelete, true);
  ULVL := TULVLObj(ULV.FindOrAdd(ULVLID, ''));
    ULVL.SetFlag(rfCantDelete, true);
  ULVO := TULVOObj(ULV.FindOrAdd(ULVOID, ''));
    ULVO.SetFlag(rfCantDelete, true);
end;
{/v0.09}

{v0.11}
procedure TAcqData.CheckULMRecords;
begin
  ULMF := TULMFObj(ULM.FindOrAdd(ULMFID, ''));
    ULMF.SetFlag(rfCantDelete, true);
  ULM_ULP := TULPObj(ULM.FindOrAdd(ULPID, ''));
    ULM_ULP.SetFlag(rfCantDelete, true);
  ULM_ULB := TULBObj(ULM.FindOrAdd(ULBID, ''));
    ULM_ULB.SetFlag(rfCantDelete, true);
end;
{/v0.11}

function TAcqData.GetNonameFileName: string;
begin
  Result := {v0.29}DataDir + {/v0.29} 'NONAME_' + ULI.ChannelName + ULFExt;
end;

procedure TAcqData.CreateTemplate(const AFileName: string);
begin
  ULF.Free;
  ULF := TULFObj.Create(nil);
  CheckULRecords;
  ULF.FileName := ChangeFileExt(AFileName, ULTExt);
end;

{v0.24}
function TAcqData.GetRawFileName(Index: integer): string;
begin
  Result := GetChannelFileName({v0.29}DataDir,{/v0.29}ULI.ChannelName) + IntToStr(Index);
end;

function TAcqData.GetActiveData: TStream;
begin
  Result := ActiveULADRc.Data;
end;

function TAcqData.GetActiveULADRc: TULADRc;
begin
  Result := ULAD[ULADIndex];
end;

function TAcqData.GetDataCount: integer;
begin
  Result := ULAD.Count;
end;

function TAcqData.GetDataIndex: integer;
begin
  Result := ULADIndex;
end;

procedure TAcqData.SetDataIndex(Index: integer);
begin
  if (Index >= 0) and (Index < ULAD.Count) then begin
    ULADIndex := Index;
  end else begin
    raise EAcqData.Create('SetDataIndex OOR ' + IntToStr(Index));
  end;
end;
{/v0.24}

procedure TAcqData.SetFileName(const AFileName: string; AMode: TOpenMode);
var
  e: string;
{  s: TFileStream;}
{  ei: TULEObj;}
  fn: string;
  rfn: string;
  i:integer;
{  frm: TAnalSetupForm;}
{  r:integer;}
  ud: TULADObj;
begin
  FMode := AMode;
  {v0.20}{/v0.20 BufPointsClear;}
  ClearScanInfo;
  e := UpperCase(ExtractFileExt(AFileName));
  fn := AFileName;
  if FMode <> omCreate then begin

    if not FileExists(AFileName) then
      raise EFOpenError.Create(GetTxt({#}'Can not open file') + ' ' + AFileName);
    ULF.Free;
    ULF := TULFObj.Create(nil);
    if (e = ULFExt) or (e = ASCExt) {v0.14} or (e = ULTExt) {/v0.14} then
      ULF.LoadFromFile(AFileName);
    if FMode = omReadOnly then
      ULF.ReadOnly := true;
    CheckULRecords;
    if (e <> ULFExt) and (e <> AscExt) {v0.14} and (e <> ULTExt) {/v0.14} then begin
      {v0.18}
      if TryConvertFromFile(AFileName, ActiveData) then begin
        {OK}
      end else
      begin
      {/v0.18
        if e = DatExt then begin
          if not ULA.FindObj(ULEID, foFromRoot, DatExt, TULObj(ei)) then
            ei := TULEObj(ULA.Add(ULEID));
          FillDatExpImp(ei);
          s := TFileStream.Create(AFileName, fmOpenRead);
          ULConvertFrom(ei, s, ULAD.Data);
          s.Free;
        end else if e = ULDExt then begin
          if not ULA.FindObj(ULEID, foFromRoot, ULDExt, TULObj(ei)) then
            ei := TULEObj(ULA.Add(ULEID));
          FillULDExpImp(ei);
          s := TFileStream.Create(AFileName, fmOpenRead);
          ULConvertFrom(ei, s, ULAD.Data);
          s.Free;
        end else if e = TXTExt then begin
          if not ULA.FindObj(ULEID, foFromRoot, TxtExt, TULObj(ei)) then
            ei := TULEObj(ULA.Add(ULEID));
          FillTXTExpImp(ei);
          if ei.EditModal <> mrOK then
            raise EImportAborted.Create('Import aborted ' + AFileName);
          s := TFileStream.Create(AFileName, fmOpenRead);
          ULConvertFrom(ei, s, ULAD.Data);
          s.Free;
        end else begin
        }
        raise EInvalidFileExt.Create(GetTxt({#}'Invalid file extension') + ': ' + AFileName);
      end;
      ULF.FileName := ReplaceExt(AFileName, ULFExt, true);
    end;
    {v0.20}
    {v0.24}
    ULAD.CurDataSizeUpdate;
    {/v0.24
    FCurDataSize := ActiveData.Size;}
    {/v0.20}
    ScanData;
    {v0.45}
    {$IFNDEF CONSOLE}
    if AutofixData then begin
      ChkAcqData(Self, true);{chkulfu}
    end;
    {$ENDIF}
    {/v0.45}
  end else begin

    if (e <> ULFExt) {v0.13} and (e <> ULTExt){/v0.13} then
      raise EOnlyULFSupported.Create(GetTxt({#}'Not .ULF or .ULT extension') + ': ' + AFileName);
    ULF.Free;
    ULF := TULFObj.Create(nil);
    {v0.13}
    if FileExists(fn) then begin
      ULF.LoadFromFile(fn);
    end{v0.14} else begin
      raise EAcqData.Create(GetTxt({#}'Template Not Found') + ': ' + fn);
    end{/v0.14};
    CheckULRecords;
    {v0.34}
    FTemplateULADCount := ULAD.Count;
    {/v0.34}
    fn := GetNonameFileName;
    ULF.FileName := fn;
    {v0.31}
    {$IFNDEF CONSOLE}
    CheckChannel;
    {$ENDIF}
    {/v0.31}
    {v0.24
    RawFileName := GetChannelFileName(ULI.ChannelName);} {report ulatype}
    try
      {v0.24}
      for i := 0 to ULAD.Count - 1 do
      {/v0.24}
      begin
        ud := ULAD[i].ULAD;
        rfn := RawFileName[i];
        ud.Data := TFileStream.Create(rfn, fmCreate);
      end;
    except
      on EFCreateError do begin
        (*ShowMessage('Acquisition window already opened for ' +
          {v0.14}ULI.ChannelName{/v0.14 DefPortName}, smError, 0);*)
        raise EAcqAlreadyOpened.Create(GetTxt({#}'Acquisition window already opened for') +
          ' ' + {v0.14}ULI.ChannelName{/v0.14 DefPortName});
      end;
    end;
    {v0.13}
    {v0.14}{/v0.14
    CurULObj := ULA;
    frm := TAnalSetupForm.Create(Application.MainForm);
    r := frm.ShowModal;
    frm.Free;
    if r <> mrOK then
      raise EUserAborted.Create('Aborted by user');}
    {/v0.13}
  end;
end;

{v0.31}
procedure TAcqData.WMAppMessage(var Message: TMessage);
begin
  case Message.wParam of
    cmULObjUpdated: begin
      if TULIObj(Message.LParam) = ULI then begin
        {$IFNDEF CONSOLE}
        CheckChannel;
        {$ENDIF}
      end;
    end;
  end;
  inherited;
end;

{$IFNDEF CONSOLE}
procedure TAcqData.CheckChannel;
var
  i, di: integer;
  ul: TULADObj;
  d: TULDRObj;
  {uladr: TULADRc;}
  fnd:boolean;
  dm: TDeviceMode;
  delcnt:integer;
begin
  if FChannel <> nil then begin
    FChannel.ULN.SetFlag(rfCantDelete, false);
  end;
  FChannel := Channels.FindChannel(ULI.ChannelName);
  if FChannel <> nil then begin
    FChannel.ULN.SetFlag(rfCantDelete, true);
    dm := FChannel.DeviceMode;

    {check for added devices}
    for i := 0 to FChannel.DeviceCount - 1 do begin
      d := FChannel.Devices[i].ULDR;
      if d.DeviceType = dtDetector then begin
        fnd := false;
        for di := 0 to ULAD.Count - 1 do begin
          {v0.34}
          if ULAD[di].Addr = 0 then
            ULAD[di].Addr := StrToInt(d.AddrStr);
          {/v0.34}
          if (StrToInt(d.AddrStr) = ULAD[di].Addr) and (d.DeviceMode = dm) then begin
            fnd := true;
            break;
          end;
        end;
        if not fnd then begin
          {ul := TULADObj}(ULA.Add(ULADID));
          ULAD.Update;
          di := ULAD.Count - 1;
          {ul.DataName := d.DeviceName;!!! fix later to work with AAA
          if di > 0 then
            ul.DataName := d.DeviceName;}
        end;
        ULAD[di].Addr := StrToInt(d.AddrStr);
        if pos('REFR', Uppercase(d.DeviceName)) > 0 then begin
          ULAD[di].PointTimeInterval := 100;
          {ULAD[di].YCoef := 1;}
        end;
      end;
    end;
    {/check for added devices}

    {check for removed devices}
    di := 0;
    delcnt := 0;
    while di < ULAD.Count do begin
      fnd := false;
      for i := 0 to FChannel.DeviceCount - 1 do begin
        d := FChannel.Devices[i].ULDR;
        if (d.DeviceType = dtDetector) and (d.DeviceMode = dm) then begin
          if StrToInt(d.AddrStr) = ULAD[di].Addr then begin
            fnd := true;
            break;
          end;
        end;
      end;

      if not fnd then begin
        ul := ULAD[di].ULAD;
        if (ul.Data.Size = 0) {v0.34} and (di > (FTemplateULADCount - 1) ){/v0.34} then begin
          ULAD.DelUlad(ul);
          ul.Free;
          inc(delcnt);
        end else begin
          inc(di);
        end;
      end else
        inc(di);
    end;
    if delcnt > 0 then
      ULAD.Update;
    {/check for removed devices}
  end;
end;
{$ENDIF}
{/v0.31}

procedure TAcqData.ScanData;
var
  ap:TExpPoint;
{v0.25}
  i: integer;
{/0.25}
begin
  {v0.33}
  Reset;
  {/v0.33}
  {v0.25}
  ClearScanInfo;
  for i := 0 to DataCount - 1 do
  begin
    DataIndex := i;
  {/v0.25
  Reset;
  ClearScanInfo;}
    while ReadPoint(ap) do
      CheckPoint(ap);
    Reset;
  {v0.25}
  end;
  {v0.41}
  if DataCount > 0 then
  {/v0.41}
    DataIndex := 0;
  {/v0.25}
end;

procedure TAcqData.FixPoint(var APoint:TExpPoint);
begin
  if (APoint.y > SpectrumOpt.Limit.Max.Y) then
    APoint.y := SpectrumOpt.Limit.Max.Y;
  if (APoint.y < SpectrumOpt.Limit.Min.Y) then
    APoint.y := SpectrumOpt.Limit.Min.Y;
end;

procedure TAcqData.CheckPoint(const APoint:TExpPoint);
begin
  if APoint.x < SpectrumOpt.Extreme.Min.X then
    SpectrumOpt.Extreme.Min.X := APoint.x;

  if APoint.y < SpectrumOpt.Extreme.Min.Y then
    SpectrumOpt.Extreme.Min.Y := APoint.y;

  if APoint.x > SpectrumOpt.Extreme.Max.X then
    SpectrumOpt.Extreme.Max.X := APoint.x;

  if APoint.y > SpectrumOpt.Extreme.Max.Y then
    SpectrumOpt.Extreme.Max.Y := APoint.y;
end;

procedure TAcqData.ClearScanInfo;
begin
  SpectrumOpt.Extreme.Max := SpectrumOpt.Limit.Min;
  SpectrumOpt.Extreme.Min := SpectrumOpt.Limit.Max;
end;

procedure TAcqData.Reset;
begin
  {v0.24}
  ULAD.Reset;
  {/v0.24 ULAD.Data.Position := 0;}
  ULAF.Data.Position := 0;
  {v0.20}
  {v0.24}{/v0.24 FCurDataPos := 0;}
  {/v0.20}
  {v0.21}
  FCurDiluteCount := 0;
  {/v0.21}
  CurIndex := 0;
  {v0.20}{/v0.20 BufPointsClear;}
end;

procedure TAcqData.Seek(APos:longint);
begin
  {v0.24}
  ActiveData.Position := APos;
  ActiveULADRc.CurDataPos := APos;
  {/v0.24
  ULAD.Data.Position := APos;}
  ULAF.Data.Position := APos;
  {v0.20}
  {v0.24}{/v0.24 FCurDataPos := APos;}
  {/v0.20 BufPointsClear;}
  {v0.21}
  FCurDiluteCount := 0;
  {/v0.21}
end;

procedure TAcqData.SeekPoint(Index:Longint);
begin
  {v0.45}
  if Index < 0 then begin
    Seek(ActiveULADRc.CurDataPos + sizeof(TExpPoint) * Index);
    CurIndex := CurIndex + Index;
    exit;
  end;
  {/v0.45}
  {v0.20}
  {v0.24}
  Seek(Index * sizeof(TExpPoint));
  {/v0.24
  FCurDataPos := Index * sizeof(TExpPoint);
  Seek(FCurDataPos);}
  {/v0.20
  Seek(Index * sizeof(TExpPoint));}
  CurIndex := Index;
end;

function TAcqData.WritePoint(var APoint:TExpPoint):boolean;
var
  urc: TULADRc;
{v0.45}
  s: TStream;
{/v0.45}
begin
  {v0.20}
  {v0.24}
  urc := ActiveULADRc;
  {v0.45}
  s := urc.Data;
  if s is TMemoryStream then begin
    if urc.CurDataPos + sizeof(APoint) > urc.CurDataSize then begin
      s.WriteBuffer(APoint, sizeof(APoint));
      urc.CurDataSize := s.Size;
      urc.CurDataPos := urc.CurDataSize;
    end else begin
      move(APoint, PLongByteArray(TMemoryStream(s).Memory)^[urc.CurDataPos], sizeof(APoint));
      inc(urc.CurDataPos, sizeof(APoint));
      {Result := true;}
    end;
  end else begin
    s.WriteBuffer(APoint, sizeof(APoint));
    urc.CurDataSize := s.Size;
    urc.CurDataPos := urc.CurDataSize;
  end;
  {/v0.45
  urc.Data.WriteBuffer(APoint, sizeof(APoint));
  inc(urc.CurDataSize, sizeof(APoint));}

  {/v0.24
  ULAD.Data.WriteBuffer(APoint, sizeof(APoint))
  inc(FCurDataSize, sizeof(APoint));}
  {/v0.20}
  Result := true;
end;

function TAcqData.ReadPoint(var APoint:TExpPoint):boolean;
{v0.20}
var s: TStream;
{/v0.20}
{v0.24}
var urc: TULADRc;
{/v0.24}
begin
  if UseFiltered then begin
    {v0.20}
    s := ULAF.Data;
    {/v0.20
    ReadPoint := (ULAF.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint));}
  end else begin
    {v0.20}
    s := {v0.24}ActiveData{/v0.24 ULAD[ULADIndex].Data};
    {/v0.20
    ReadPoint := (ULAD.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint));}
  end;

  {v0.21}
  repeat
  {/v0.21}

    {v0.20}
    if s is TMemoryStream then begin
      urc := ActiveULADRc;
      if urc.CurDataPos{FCurDataPos} + sizeof(APoint) > urc.CurDataSize{FCurDataSize} then begin
        Result := false
      end else begin
        move({v0.45 changed PByteArray}{/v0.45}PLongByteArray(TMemoryStream(s).Memory)^[urc.CurDataPos{FCurDataPos}], APoint, sizeof(APoint));
        inc(urc.CurDataPos{FCurDataPos}, sizeof(APoint));
        Result := true;
      end;
    end else begin
      Result := (s.Read(APoint, sizeof(APoint)) = sizeof(APoint))
    end;
    {/v0.20}

  {v0.21}
    if (not FApplyMath) or (not Result) then
      break;

    inc(FCurDiluteCount);
    if FCurDiluteCount >= FDiluteCount then begin
      FCurDiluteCount := 0;
      break;
    end else begin
      continue;
    end;

    break;
  until false;
  {/v0.21}

  {v0.11}
  if FApplyMath then begin
    APoint.X := APoint.X * FMultiplyX + FAddX;
    APoint.Y := APoint.Y * FMultiplyY + FAddY;
  end;
  {/v0.11}
(*
  ReadPoint := false;
  if Stream <> NoHIO then begin
    {v0.05}
    case FFilterPars.FilterType of
      ftNone: begin
        ReadPoint := (IORead(Stream, APoint, sizeof(TExpPoint), r) = irOK) and (r > 0);
      end;

      ftAvg: begin
        if BufPointCount = BufPointPos then begin
          ir := IORead(Stream, BufPoints, FFilterPars.AvgPointCount * sizeof(TExpPoint), r);
          BufPointCount := r div sizeof(TExpPoint);
          if (ir <> irOK) or (BufPointCount = 0) then
            exit;
          y := 0;
          for r := 0 to BufPointCount - 1 do begin
            y := BufPoints[r].Y + y;
          end;
          y := y / BufPointCount;
          for r := 0 to BufPointCount - 1 do begin
            BufPoints[r].Y := y;
          end;
          BufPointPos := 0;
        end;
        APoint := BufPoints[BufPointPos];
        inc(BufPointPos);
        ReadPoint := true;
      end;

      ftFilter1: begin
        ir := IORead(Stream, APoint, sizeof(TExpPoint), r);
        if (ir <> irOK) or (r = 0) then
          exit;
        if BufPointCount = 0 then begin
          BufPoints[0] := APoint;
          BufPointCount := 1;
        end else begin
          APoint.Y :=  BufPoints[0].Y * FFilterPars.Filter1 +
              (1 - FFilterPars.Filter1) * APoint.Y;
          BufPoints[0] := APoint;
        end;
        ReadPoint := true;
      end;
    end;
  end;
*)
end;

function TAcqData.ReadIndexPoint(var APoint: TIndexPoint): boolean;
var p: TExpPoint;
begin
  if ReadPoint(p) then begin
    APoint.Point := p;
    APoint.Index := CurIndex;
    inc(CurIndex);
    ReadIndexPoint := true;
  end else
    ReadIndexPoint := false;
end;

function TAcqData.GetSize: longint;
begin
  GetSize := {v0.24}ActiveData.Size;{ULAD[ULADIndex].Data.Size;}
end;

function TAcqData.GetPointCount: longint;
begin
  Result := GetSize div sizeof(TExpPoint);
end;

procedure TAcqData.Clear;
begin
  {v0.20}
  Truncate(0);
  {/v0.20
  Reset;
  ULAD.Data.Size := 0;
  ULAF.Data.Size := 0;}
end;

{v0.45 obsolete}
{/v0.45
procedure TAcqData.Error(Code,Info:integer);
begin
  case Code of
    coIndexError:
      ShowMessage(GetTxt('Collection index out of range (TAcqData)')
        , smError, 0);
    coOverflow:
      ShowMessage(GetTxt('Collection overflow. (TAcqData)'),smError, 0);
  end;
end;}

{
procedure TAcqData.SetLimits(MinX:TXValue; MinY:TYValue;
  MaxX:TXValue; MaxY:TYValue);
begin
  SpectrumOpt.Limit.Min.X := MinX;
  SpectrumOpt.Limit.Min.Y := MinY;
  SpectrumOpt.Limit.Max.X := MaxX;
  SpectrumOpt.Limit.Max.Y := MaxY;
end;
}
{
procedure TAcqData.SetUserLimit(AUserLimit: TUserViewLimit);
begin
  SpectrumOpt.UserLimit := AUserLimit;
end;
}
{
procedure TAcqData.SetUserLimit(xorigin, xsize, yorigin, ysize : real);
{the numbers used to describe the data}
{begin
{  DrawOpt.Origin.X := xorigin;
  DrawOpt.Origin.Y := yorigin;
  DrawOpt.Size.X := xsize;
  DrawOpt.Size.Y := ysize;}
{end;}

function TAcqData.GetPoint(Index: TPointCount; var APoint: TExpPoint): boolean;
var
{  r: longint;}
  s: TStream;
begin
  GetPoint := false;
  if UseFiltered then
    s := ULAF.Data
  else
    s := {v0.24}ActiveData{/v0.24 ULAD[ULADIndex].Data};
  if (Index < 0) or (s.Size <= (Index * sizeof(TExpPoint))) then
    exit;
  s.Position := Index * sizeof(TExpPoint);
  s.ReadBuffer(APoint, sizeof(TExpPoint));
  {v0.11}
  if FApplyMath then begin
    APoint.X := APoint.X * FMultiplyX + FAddX;
    APoint.Y := APoint.Y * FMultiplyY + FAddY;
  end;
  {/v0.11}
  GetPoint := true;
  {(IORead(Stream, APoint, sizeof(TExpPoint), r) = irOK);;}
end;

function TAcqData.GetIndexPoint(Index: LongInt; var APoint: TIndexPoint): boolean;
var
  p: TExpPoint;
begin
  if GetPoint(Index, p) then begin
    APoint.Point := p;
    APoint.Index := Index;
    GetIndexPoint := true;
  end else
    GetIndexPoint := false;
end;

procedure TAcqData.PutPoint(Index: Longint; APoint: TExpPoint);
{v0.24}
var
  d: TULADRc;
{/v0.24}
begin
  {v0.24}
  d := ActiveULADRc;
  d.Data.Position := Index * SizeOf(APoint);;
  d.Data.WriteBuffer(APoint, sizeof(APoint));
  {/v0.24
  ULAD.Data.Position := Index * SizeOf(APoint);;
  ULAD.Data.WriteBuffer(APoint, sizeof(APoint));
  }
end;

procedure TAcqData.AddPoint(var APoint: TExpPoint);
var d: TStream;
begin
  FixPoint(APoint);
  CheckPoint(APoint);
  {v0.24}
  d := ActiveData;
  PutPoint(d.Size div sizeof(TExpPoint), APoint);
  {/v0.24
  PutPoint(ULAD.Data.Size div sizeof(TExpPoint), APoint);}

{  if Stream <> NoHIO then begin
    PutPoint((IOGetPropInt(Stream, ipSize) - HeadSizeInFile)
      div sizeof(TExpPoint), APoint);
  end;
}
end;

{v0.24}
procedure TAcqData.ChangeFileName(const AFileName: string);
begin
  ULF.FileName := ChangeFileExt(AFileName, ULFExt);
end;
{/v0.24}

procedure TAcqData.SaveTo(const AFileName: string);
  { Saves current data to AFileName and set it as the current file. }
var
  s: TFileStream;
  e: TULEObj;
  ext: string;
begin
  ext := UpperCase(ExtractFileExt(AFileName));
  {v0.18}
  if (ext = UlfExt) or (ext = AscExt) or (ext = UltExt) then begin
    if ext = UltExt then begin
      GetReadyForTemplate;
    end;
    ULF.SaveToFile(AFileName);
  end else begin
    if ExpImp.HasChildWithFieldUsrValue(fnExtName, ext, TULObj(e)) then begin
      s := TFileStream.Create(AFileName, fmCreate);
      try
        ULConvertTo(e, {v0.24} ActiveData{/v0.24 ULAD[ULADIndex].Data}, s);
      finally
        s.Free;
      end;
    end else begin
      raise EInvalidFileExt.Create(GetTxt({#}'Unsupported file extension') + ': ' + AFileName);
    end;
  end;
  {/v0.18
  if ext = DatExt then begin
    s := TFileStream.Create(AFileName, fmCreate);

    if not ULF.FindObj(ULEID, foFromRoot, DatExt, TULObj(e)) then begin
      e := TULEObj(ULA.Add(ULEID));
      FillDatExpImp(e);
    end;
    ULConvertTo(e, ULAD.Data, s);
    s.Free;
  end else if ext = TxtExt then begin
    s := TFileStream.Create(AFileName, fmCreate);
    if not ULF.FindObj(ULEID, foFromRoot, TxtExt, TULObj(e)) then begin
      e := TULEObj(ULA.Add(ULEID));
      FillTxtExpImp(e);
    end;
    ULConvertTo(e, ULAD.Data, s);
    s.Free;
  end else if (ext = UlfExt) or (ext = AscExt) or (ext = UltExt) then begin
    if ext = UltExt then begin
      GetReadyForTemplate;
    end;
    ULF.SaveToFile(AFileName);
  end else begin
    raise EInvalidFileExt.Create('Invalid file extension: ' + AFileName);
  end;   }
end;

{v0.15}
procedure TAcqData.GetReadyForTemplate;
begin
  ULP.Clear;
  ULB.Clear;
  {v0.24}
  ULAD.SetZeroSizes;
  {/v0.24
  ULAD.Data.Size := 0;}
  ULI.SetFlag(rfWriteLocked, false);
end;
{/v0.15}

procedure TAcqData.GrowShowLimits(dX:TScreenX; dY:TScreenY);
begin
  SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Max.X + dx;
  if SpectrumOpt.ViewLimit.Max.X > SpectrumOpt.Limit.Max.X then
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.Limit.Max.X
  else if SpectrumOpt.ViewLimit.Min.X < SpectrumOpt.Limit.Min.X then
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.Limit.Min.X;

  SpectrumOpt.ViewLimit.Max.Y :=  SpectrumOpt.ViewLimit.Max.Y + dy;
  if SpectrumOpt.ViewLimit.Max.Y > SpectrumOpt.Limit.Max.Y then
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Max.Y
  else if SpectrumOpt.ViewLimit.Max.Y < SpectrumOpt.Limit.Min.Y then
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Min.Y;
end;

procedure TAcqData.MoveShowLimits(dx,dy:integer);
var
  curdx:TXValue;
  curdy:TYValue;
begin
  curdx := SpectrumOpt.ViewLimit.Max.X - SpectrumOpt.ViewLimit.Min.X;
  curdy := SpectrumOpt.ViewLimit.Max.Y - SpectrumOpt.ViewLimit.Min.Y;
  if dx > 0 then begin
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Max.X + dx;
    if SpectrumOpt.ViewLimit.Max.X > SpectrumOpt.Limit.Max.X then
      SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.Limit.Max.X;
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.ViewLimit.Max.X - curdx;
  end else if dx < 0 then begin
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.ViewLimit.Min.X + dx;
    if SpectrumOpt.ViewLimit.Min.X < SpectrumOpt.Limit.Min.X then
      SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.Limit.Min.X;
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Min.X + curdx;
  end;

  if dy > 0 then begin
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.ViewLimit.Max.Y + dy;
    if SpectrumOpt.ViewLimit.Max.Y > SpectrumOpt.Limit.Max.Y then
      SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Max.Y;
    SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.ViewLimit.Max.Y - curdy;
  end else if dy < 0 then begin
    SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.ViewLimit.Max.Y + dy;
    if SpectrumOpt.ViewLimit.Min.Y < SpectrumOpt.Limit.Min.Y then
      SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.Limit.Min.Y;
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.ViewLimit.Min.Y + curdy;
  end;
end;

function TAcqData.Current: TPointCount;
begin
  if UseFiltered then
    Current := ULAF.Data.Position div sizeof(TExpPoint)
  else
    Current := ActiveULADRc.Data.Position div sizeof(TExpPoint);
end;

{procedure TAcqData.SetScreenDisp(const AScreenDisp:TScreenDisp);
begin
  ScreenDisp := AScreenDisp;
end;}

procedure TAcqData.SetViewLimit(const AViewLimit:TExpViewLimit);
{v0.13}
var uvl:TUserViewLimit;
{/v0.13}
begin
  SpectrumOpt.ViewLimit := AViewLimit;
  {v0.15}
{  if abs(SpectrumOpt.ViewLimit.Max.X) > (MaxSingle / 100) then begin
    SpectrumOpt.ViewLimit.Max.X := 20*60;
    SpectrumOpt.ViewLimit.Min.X := 0;
  end;
  if abs(SpectrumOpt.ViewLimit.Max.Y) > (MaxSingle / 100) then begin
    SpectrumOpt.ViewLimit.Max.Y := DefMaxAcqYValue;
    SpectrumOpt.ViewLimit.Min.Y := DefMinUsrYValue;;
  end;}
  {/v0.15}
  SpectrumOpt.ViewLimit.Size.X := SpectrumOpt.ViewLimit.Max.X - SpectrumOpt.ViewLimit.Min.X;
  SpectrumOpt.ViewLimit.Size.Y := SpectrumOpt.ViewLimit.Max.Y - SpectrumOpt.ViewLimit.Min.Y;
  if (SpectrumOpt.ViewLimit.Size.X = 0) or (SpectrumOpt.ViewLimit.Size.Y = 0) then begin
    ShowMessage(GetTxt({#}'Zero spectrum ViewLimit size'), smError, 0);
  end;
  {v0.13}
  with SpectrumOpt do begin
    ExpToUser(ViewLimit.Min, uvl.Min);
    ExptoUser(ViewLimit.Max, uvl.Max);
    ULVL.MinX := uvl.Min.X;
    ULVL.MinY := uvl.Min.Y;
    ULVL.MaxX := uvl.Max.X;
    ULVL.MaxY := uvl.Max.Y;
  end;
  {/v0.13}
end;

procedure TAcqData.SetLimit(const ALimit:TExpViewLimit);
begin
  SpectrumOpt.Limit := ALimit;{just for now }
  SpectrumOpt.Limit.Size.X := SpectrumOpt.Limit.Max.X - SpectrumOpt.Limit.Min.X;
  SpectrumOpt.Limit.Size.Y := SpectrumOpt.Limit.Max.Y - SpectrumOpt.Limit.Min.Y;
  if (SpectrumOpt.Limit.Size.X = 0) or (SpectrumOpt.Limit.Size.Y = 0) then begin
    ShowMessage(GetTxt({#}'Zero spectrum limit size'), smError, 0);
  end;
end;

function TAcqData.ExpToScreen(const AExpPoint:TExpPoint; const AScreenDisp: TScreenDisp;
  var AScreenPoint:TScreenPoint): integer;
{ prevod exp. hodnoty na grafickou, za pomoci graphic limit
  hodnot, returns <> 0 if AScreenPoint is out of the AScreenDisp}
{var
  x:TXValue;
  y:TYValue;
  r:single; }
begin
  Result := 0;
  if AExpPoint.X > SpectrumOpt.ViewLimit.Max.X then begin
    Result := Result or opRight;
    if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then
      Result := Result or opAbove
    else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then
      Result := Result or opBelow;
    {exit;}
  end else if AExpPoint.X < SpectrumOpt.ViewLimit.Min.X then begin
    Result := Result or opLeft;
    if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then
      Result := Result or opAbove
    else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then
      Result := Result or opBelow;
    {exit;}
  end;

  if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then begin
    {if ScreenDisp.ZeroIsOnTop then }
    begin
      AScreenPoint.Y := AScreenDisp.Top;
    end
    { else begin
      AScreenPoint.Y := ScreenDisp.Top - ScreenDisp.Height + 1;
    end};
  end else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then begin
     {if ScreenDisp.ZeroIsOnTop then}
     begin
       AScreenPoint.Y := AScreenDisp.Bottom;{AScreenDisp.Top + AScreenDisp.Height - 1;}
     end{ else begin
       AScreenPoint.Y := ScreenDisp.Top - ScreenDisp.Height + 1;
     end};
  end else begin
    {if ScreenDisp.ZeroIsOnTop then }
    begin
      AScreenPoint.Y := AScreenDisp.Bottom + RectInclusive{AScreenDisp.Top + AScreenDisp.Height}
      - round(
        (AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive){AScreenDisp.Height} *
        (AExpPoint.Y - SpectrumOpt.ViewLimit.Min.Y) /
        (SpectrumOpt.ViewLimit.Size.Y)
       );
    end{ else begin
      AScreenPoint.Y := ScreenDisp.Top - ScreenDisp.Height + round(
        ScreenDisp.Height *
        (AExpPoint.Y - SpectrumOpt.ViewLimit.Min.Y) /
        (SpectrumOpt.ViewLimit.Size.Y)
      );

    end};
  end;
  AScreenPoint.X := AScreenDisp.Left + round(
    (AScreenDisp.Right - AScreenDisp.Left + RectInclusive){AScreenDisp.Width} *
    (AExpPoint.X - SpectrumOpt.ViewLimit.Min.X) / SpectrumOpt.ViewLimit.Size.X
  );
end;

function TAcqData.ExpSizeToScreenSize(const AExpPoint:TExpPoint; const AScreenDisp: TScreenDisp;
  var AScreenPoint:TScreenPoint): integer;
  { conversion of distance in experimental units to distance in screen points }
{var
  x:TXValue;
  y:TYValue;
  r:single;}
begin
  Result := 0;
  AScreenPoint.Y := round((AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive){AScreenDisp.Height} *
    AExpPoint.Y / SpectrumOpt.ViewLimit.Size.Y);
  AScreenPoint.X := round((AScreenDisp.Right - AScreenDisp.Left + RectInclusive){AScreenDisp.Width} *
    AExpPoint.X / SpectrumOpt.ViewLimit.Size.X);
end;

function TAcqData.ScreenToExp(const ScreenPoint: TScreenPoint; const AScreenDisp: TScreenDisp;
  var AExpPoint: TExpPoint): boolean;
var
  r:real;
begin
  ScreenToExp := false;
{  if AScreenDisp.Width = 0 then
    exit;
  if AScreenDisp.Height = 0 then
    exit;
}
  if AScreenDisp.Left = AScreenDisp.Right then
    exit;
  if AScreenDisp.Top = AScreenDisp.Bottom then
    exit;
  r := ScreenPoint.X - AScreenDisp.Left;
  r := r *(SpectrumOpt.ViewLimit.Size.X) / (AScreenDisp.Right - AScreenDisp.Left + RectInclusive);{AAScreenDisp.Width;}
  AExpPoint.X := SpectrumOpt.ViewLimit.Min.X + r;
  AExpPoint.Y := SpectrumOpt.ViewLimit.Min.Y +
    (AScreenDisp.Bottom + RectInclusive{AScreenDisp.Top + AScreenDisp.Height} - ScreenPoint.Y) *
     SpectrumOpt.ViewLimit.Size.Y / (AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive){AScreenDisp.Height};

  ScreenToExp := true;
end;

function TAcqData.FindDataPoint(const AExpPoint:TExpPoint;
  var ADataPoint:TExpPoint; var Index:TPointCount):boolean;
var
  l, h, i, c:longint;
  p:TExpPoint;
{  found:boolean;}
begin
{ FindDataPoint := false;}
  Index := -1;
  l := 0;
  h := GetPointCount{NumPoints} - 1;
  while l <= h do begin
    i := (l + h) shr 1;
    GetPoint(i, p);
    if p.x < AExpPoint.X then
      c := -1
    else if p.x > AExpPoint.X then
      c := 1
    else
      c := 0;
    if c < 0 then
      L := I + 1
    else begin
      h := i - 1;
      if c = 0 then begin
         {found := True;}
{      if not Duplicates then L := I;}
         l := i;
      end;
    end;
  end;
  Index := l;
  ADataPoint := p;
  FindDataPoint := true;
end;

function TAcqData.UserToExp(const AUserPoint: TUserPoint;
  var AExpPoint: TExpPoint): boolean;
begin
  AExpPoint.X := AUserPoint.X * 60;
  AExpPoint.Y := AUserPoint.Y;
  UserToExp := true;
end;

function TAcqData.ExpToUser(const AExpPoint:TExpPoint;
      var AUserPoint:TUserPoint):boolean;
begin
  AUserPoint.X := AExpPoint.X / 60;
  AUserPoint.Y := AExpPoint.Y;
  ExpToUser := true;
end;

function TAcqData.UserToScreen(const AUserPoint: TUserPoint;
  const AScreenDisp: TScreenDisp; var AScreenPoint: TScreenPoint): boolean;
var ap:TExpPoint;
begin
  UserToScreen := false;
  if not UserToExp(AUserPoint, ap) then
    exit;
  if ExpToScreen(ap, AScreenDisp, AScreenPoint) <> 0 then
    exit;
  UserToScreen := true;
end;

function TAcqData.UserSizeToScreenSize(const AUserPoint:TUserPoint;
  const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint):boolean;
var ap:TExpPoint;
begin
  Result := false;
  if not UserToExp(AUserPoint, ap) then
    exit;
  if ExpSizeToScreenSize(ap, AScreenDisp, AScreenPoint) <> 0 then
    exit;
  Result := true;
end;

procedure TAcqData.FilterUpdate;
begin
end;

destructor TAcqData.Destroy;
var
  i: integer;
  d: TULADRc;
begin
  {v0.31}
  if FChannel <> nil then begin
    if FChannel <> nil then
      FChannel.ULN.SetFlag(rfCantDelete, false);
  end;
  {/v0.31}
  try
    if (ULF <> nil) and (ULF.FileName <> '') {v0.09} and ULF.Modified {/v0.09} then begin
      {v0.45}
      if FCreatedOK then
      {/v0.45}
        ULF.SaveToFile('');
      {v0.24}
      for i := 0 to ULAD.Count - 1 do begin
        if RawFileName[i] <> '' then begin
          d := ULAD[i];
          if d.Data is TFileStream then begin
            d.ULAD.Data := nil;
            DeleteFile(RawFileName[i]);
          end;
        end;
      end;
      {/v0.24
      if RawFileName <> '' then begin
        ULAD[ULADIndex].Data := nil;
        DeleteFile(RawFileName);
      end;}
    end;
  finally
    {v0.24}
    ULAD.Free;
    {/v0.24}
    {v0.31}
    ULI := nil;
    {/v0.31}
    ULF.Free;
    {isOn := false;}
  end;

  {v0.14}
  if (Spectrum <> nil) then begin
    if Spectrum.AcqData <> Self then begin
      ShowMessage(GetTxt({#}'Invalid Spectrum.AcqData'), smError, 0);
    end else begin
      Spectrum.AcqData := nil;
    end;
  end;
  {/v0.14}
  inherited Destroy;
end;

{v0.05}
{
procedure TAcqData.SetFilterPars(const AFilterPars:TFilterPars);
begin
  FFilterPars := AFilterPars;
end;

procedure TAcqData.GetFilterPars(var AFilterPars:TFilterPars);
begin
  AFilterPars := FFilterPars;
end;
}

{v0.20}{/v0.20
procedure TAcqData.BufPointsClear;
begin
  BufPointCount := 0;
  BufPointPos := 0;
end;
}
{/v0.05}

{v0.11}
procedure TAcqData.MethodLoadFromFile(const AFileName: string);
var
  f: TULFObj;
  m: TULMObj;
begin
  f := TULFObj.Create(nil);
  try
    f.LoadFromFile(AFileName);
    if not f.FindObj(ULMID, foNotSelf{v0.13}{/v0.13 + foNotRecursive}, '', TULObj(m)) then begin
      ShowMessage(GetTxt({#}'No method find in file') + ' ' + AFileName, smError, 0);
      exit;
    end;
    ULM.Clear;
    ULM.Assign(m);
    ULM.MethodTemplate := AFileName;
    CheckULMRecords;
  finally
    f.Free;
  end;
end;

procedure TAcqData.MethodSaveToFile(const AFileName: string);
begin
  ULM.SaveToFile(AFileName);
  ULM.MethodFileName := AFileName;
end;

{v0.30}
procedure TAcqData.SpectrumGet(var sp: TSpectrum);
begin
  if Spectrum = nil then begin
    sp := TSpectrum.Create(Self);
  end else begin
    sp := Spectrum;
  end;
end;

procedure TAcqData.SpectrumRelease(sp: TSpectrum);
begin
  if sp <> Spectrum then
    sp.Free;
end;
{/v0.30}

procedure TAcqData.CalibrationFileSelect(const AFileName: string);
{v0.30}
var sp: TSpectrum;
{/v0.30}
begin
  {v0.38}{ulatype}
  if ULA.CalibrationStandard then
    exit;
  if ULF.FileName = AFileName then
    exit;
  ULM.UseCalibrationFile := true;
  {/v0.38}

  ULM.CalibrationFileName := AFileName;
  Cal_ULP.Free;
  Cal_ULP := nil;
  {v0.30}
  SpectrumGet(sp);
  try
    sp.PeaksAmountsCalculateCalibrationFile;
    {v0.33}
    sp.PeaksAmountsCalculateValues;{ulantype}
    {/v0.33}
  finally
    SpectrumRelease(sp);
  end;
  {/v0.30}
end;

procedure TAcqData.AcquiredDataDiscard;
begin
  {v0.24}
  ULAD.SetZeroSizes;
  {/v0.24
  ULAD[ULADIndex].Data.Size := 0;}
  ULAF.Data.Size := 0;
  ULP.Clear;
  ULB.Clear;
end;

procedure TAcqData.DoMath(mo:TDataMathOperation; r: single);
  { set value of mo corresponding variable and activate FApplyMath field
   (or desactivate if mo = moNone) }
begin
  case mo of
    moNoneOp: begin
      FMultiplyX := 1;
      FMultiplyY := 1;
      FAddX := 0;
      FAddY := 0;
      FApplyMath := false;
      {v0.21}
      FDiluteCount := 1;
      if Spectrum <> nil then
        Spectrum.RecalculatePeaks;
      {/v0.21}
      exit;
    end;
    moMultiplyX: FMultiplyX := r;
    moMultiplyY: FMultiplyY := r;
    moAddX: FAddX := r;
    moAddY: FAddY := r;
    {v0.21}
    moDilute: begin
      FDiluteCount := round(r);
      if FDiluteCount <= 0 then
        FDiluteCount := 1;
    end;
    {/v0.21}
  else
    exit;
  end;
  FApplyMath := true;
  {v0.21}
  if Spectrum <> nil then
    Spectrum.RecalculatePeaks;
  {/v0.21}
end;

procedure TAcqData.MakeMathPermanent;
var
  index: integer;
  p:TExpPoint;
begin
  index := 0;
  while GetPoint(index, p) do begin
    PutPoint(index, p);
    inc(index);
  end;
  DoMath(moNoneOp, 0);
end;
{/v0.11}

{v0.14}
procedure TAcqData.InstrumentLoadFromFile(const AFileName: string);
var
  f: TULFObj;
  m: TULIObj;
begin
  f := TULFObj.Create(nil);
  try
    f.LoadFromFile(AFileName);
    if not f.FindObj(ULIID, foNotSelf{v0.13}{/v0.13 + foNotRecursive}, '', TULObj(m)) then begin
      ShowMessage(GetTxt({#}'No Instrument found in file') + ' ' + AFileName, smError, 0);
      exit;
    end;
    ULI.Clear;
    ULI.Assign(m);
    ULI.InstrumentTemplate := AFileName;
    {CheckULIRecords;}
  finally
    f.Free;
  end;
end;

procedure TAcqData.InstrumentSaveToFile(const AFileName: string);
begin
  ULI.SaveToFile(AFileName);
  ULI.InstrumentFileName := AFileName;
end;
{/v0.14}

{v0.15}
procedure TAcqData.Truncate(AfterX: TXValue);
 { remove all acqisition data (together with peaks, baselines)
   after specified X value }
var
  i: integer;
  p: TULPRObj;
  b: TULBRObj;
  APoint: TExpPoint;
  osize:integer;
  {v0.24}
  di: integer;
  urc: TULADRc;
  {/v0.24}
begin
  i := 0;
  while i < ULP.ChildCount do begin
    p := TULPRObj(ULP.Childs[i]);
    if p.X1 > AfterX then begin
      p.Free;
    end else begin
      inc(i);
    end;
  end;
  i := 0;
  while i < ULB.ChildCount do begin
    b := TULBRObj(ULB.Childs[i]);
    if b.X1 >= AfterX then
      b.Free
    else
      inc(i);
  end;

  for di := 0 to DataCount - 1 do begin
    DataIndex := di;
    urc := ActiveULADrc;
    osize := urc.Data.Size;
    urc.Data.Position := 0;
    if AfterX > 0 then
    while
      (urc.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint)) and
      (APoint.X <= AfterX)
    do;
    urc.Data.Size := urc.Data.Position;
    urc.CurDataSize := urc.Data.Size;

    ULAF.Data.Size := urc.CurDataSize;

    if osize <> urc.Data.Size then
      ULF.Modified := true;
  end;
  Reset;

  {/v0.24
    osize := ULAD[ULADIndex].Data.Size;

    ULAD[ULADIndex].Data.Position := 0;
    if AfterX > 0 then
    while
      (ULAD[ULADIndex].Data.Read(APoint, sizeof(APoint)) = sizeof(APoint)) and
      (APoint.X <= AfterX)
    do;
    ULAD[ULADIndex].Data.Size := ULAD[ULADIndex].Data.Position;

    FCurDataSize := ULAD[ULADIndex].Data.Size;
    ULAF.Data.Size := FCurDataSize;
    if osize <> ULAD[ULADIndex].Data.Size then
      ULF.Modified := true;
  }
end;
{/v0.15}

{v0.16}
function TAcqData.GetIsTemplate: boolean;
begin
  Result := ExtractFileExt(ULF.FileName) = ULTExt;
end;
{/v0.16}

{v0.24}
function TAcqData.DataSize: integer;
begin
  Result := ActiveData.Size;{ULAD[ULADIndex].Data.Size;}
end;
{/v0.24}

{v0.25}
function TAcqData.GetActiveDataName: string;
begin
  Result := ActiveULADRc.ULAD.DataName;
end;
function TAcqData.GetUsrActiveDataName: string;
begin
  Result := ULAD[UsrDataIndex].ULAD.DataName;
end;

procedure TAcqData.SetActiveDataName(const ADataName: string);
var i: integer;
begin
  {v0.26}
  if ULAD[ULADIndex].ULAD.DataName <> ADataName then
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].ULAD.DataName = ADataName then begin
      DataIndex := i;
      break;
    end;
  end;
end;

procedure TAcqData.SetUsrActiveDataName(const ADataName: string);
var old,i: integer;
begin
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].ULAD.DataNAme = ADataName then begin
      old := UsrDataIndex;
      UsrDataIndex := i;
      DebLog('UsrDataIndex: old=' + IntToStr(old) + ' new=' + IntToStr(i));
      DataIndex := i;
      break;
    end;
  end;
end;

procedure TAcqData.UsrDataIndexReselect;
begin
  DataIndex := UsrDataIndex;
end;

{/v0.25}

{v0.30}
function TAcqData.CanClose: boolean;
begin
  Result := true;
  {$IFNDEF CONSOLE}
  if (ULF <> nil) then begin
    if (pos('NONAME', ULF.FileName) <> 0) then
    begin
      if DataSize > 0 then begin
        if not FMH.SaveAs then begin
          if ShowMessage(GetTxt({#}'Discard data?'), smNoYes, 0) <> cmYes then begin
            Result := false;
          end else begin
            AcquiredDataDiscard;
          end;
        end;
      end;
    end else begin
      if ULF.Modified then begin
        if ULF.ReadOnly then begin
          case ShowMessage(ULF.FileName + ' ' + GetTxt({#}'was opened Read Only but modified.') +
            ' ' + GetTxt({#}'Save to other file?'), smYesNoCancel, 0) of
            cmCancel: CanClose := false;
            cmYes: begin
              if not FMH.SaveAs then begin
                CanClose := false;
              end else begin
                ULF.Modified := false;
              end;
            end;
            cmNo: ULF.Modified := false;
          end;
        end else begin
          case ShowMessage(ULF.FileName, smFileModifiedSave,0) of
            cmCancel: CanClose := false;
            cmNo: ULF.Modified := false;
          end;
        end;
      end;
    end
  end;
  {$ENDIF}
end;
{/v0.30}

{v0.31}
function TAcqData.GetAddrULADIndex(AAddr: integer): integer;
  { returns ULADIndex of ULADRc, that has given AAddr; if
    not such found - returns 0 (default index) }
var
  i: integer;
begin
  Result := 0;
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].Addr = AAddr then begin
      Result := i;
      exit;
    end;
  end;
end;

procedure TAcqData.SetULI(AULI:TULIObj);
begin
  if AULI <> FULI then begin
    if FULI <> nil then
      FULI.UserUnregister(Self);
    FULI := AULI;
    if FULI <> nil then
      FULI.UserRegister(Self);
  end;
end;

function TAcqData.GetDetAddrList: string;
var i: integer;
begin
  Result := '';
  for i := 0 to ULAD.Count - 1 do begin
    {v0.46}
    if ULAD[i].Addr <> 0 then
    {/v0.46}
    begin
      if Result = '' then begin
        Result := IntToStr(ULAD[i].Addr)
      end else begin
        Result := Result + ',' + IntToStr(ULAD[i].Addr);
      end;
    end;
  end;
  if Result = '' then begin
    {v0.46}
    if (CurUVDetAddr <> 0) then
    {/v0.46}
    Result := IntToStr(CurUVDetAddr);
  end;
end;

{/v0.31}

{v0.41}
{$IFNDEF CONSOLE}
function TAcqData.GetChannel: TChannel;
begin
  CheckChannel;
  Result := FChannel; {channelsu}
end;
{$ENDIF}
{/v0.41}

{/TAcqData}

end.
