unit Spectrum;
{old version in spectru2.pas}
{$I DEFINE.PAS}
interface
uses
  SysUtils, Classes, Dialogs, Controls, Forms, TVType, MyType, MyLib, Fileu,
  ShowMsg,
  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, DebugFrm
  ;

type
  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 }
    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;
      { Baseline }
    ULP: TULPObj;
      { Acquired Peaks }
    ULV: TULVObj;
      { owner of view option records }
    ULVL: TULVLObj;
      { view limits option record }
    ULVO: TULVOObj;
      { view objects options }
    { FileName : string; tfilestream }
    { file for storing the data }
    SpectrumOpt: TSpectrumOpt;
    {ScreenDisp: TScreenDisp;
      { Size of graphic window where the drawing of points should be done }
  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 }
    {v1.05}
    {BufPoints: array[0..MaxAvgPointCount-1] of TExpPoint;}
      { Buffer used by ReadPoint method to read ahead and eventually
        do the filtering. }
    BufPointCount,
      { Number of points in buffer. }
    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 }
    procedure Seek(APos:longint);
    function GetSize:longint;
    procedure ScanData;
      { Called by SetFileName, if some data present, finds exterme 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) }
  protected
  public
    destructor Destroy; override;
    procedure BufPointsClear;
    {procedure SetFilterPars(AFilterPars:TULMFObj);
    procedure GetFilterPars(AFilterPars:TULMFObj);}
    {procedure HeadInit(var AHead:TUldRec);}
    {/v1.05}
    constructor Create(const AFileName: string; AMode: TOpenMode);
      { Name of the Analysis }
    {v0.09}
    constructor CreateFromPoints(APoints: PExpPoints; APointCount: integer);
    procedure CheckULRecords;
    {/v0.09}
    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;
    {v1.05}
    function GetPointCount:longint;
    {/v1.05}
    procedure SeekPoint(Index:Longint);
      { = Seek(Index*sizeof(TExpPoint)}
    function ReadIndexPoint(var APoint:TIndexPoint):boolean;
    {/direct stream..}
    procedure Clear;
      { Deletes all points. }
    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)}
    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 }
    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;
    property Mode: TOpenMode read FMode;
  end;

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(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;
    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 CreatePeak(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) }
      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);
      function FindBaseY(X: TXValue; var Y: TYValue):boolean;

    procedure DoPeakLimitsChanged(p: TULPRObj);

    procedure CopySelectedPeaksToMethod;
    procedure CopySelectedBaseLineToMethod;
    procedure PeaksAssignParamsFromMethod;
    procedure PeakAssignParamsFromMethod(p: TULPRObj);
    function PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;

    destructor Destroy; override;

    {v0.09}
    function ULBRToScreenPeak(BL:TULBRObj; const AScreenDisp: TScreenDisp;
      var SP:TScreenPeak): integer;
    {/0.09}

  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(APortName: string): string;
{
procedure InitPacketRec(const ApexPacket:TApexPacket; var ApexRec:TApexPacketRec);
 }
const
  SpectrumBufferSize:longint = 1024;


implementation
uses main, specform;

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(APortName: string): string;
begin
  GetChannelFileName := '$$$' + APortName + '.$$$';
end;

{TPeaks}
procedure CalculatePeaksRatios(Peaks:TULPObj);
var
  a: TAreaSize;
  cnt, i: integer;
  p: TULPRObj;
begin
  a := 0;
  cnt := Peaks.ChildCount;
  for i := 0 to cnt - 1 do begin
    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
        p.Ratio := p.AreaSize / a;
    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');
  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);
    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;
  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;
  finally
    Peaks.DoChangeUnlock;
  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
  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.CreatePeak(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
  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
    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;
  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;

  {m}f:TForm;{/m}
begin
  disp := nil;
  try
    {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 true then
    begin
      f := Application.MainForm.ActiveMDIChild;
      if (f <> nil) and (f is TSpectrumForm) then with f as TSpectrumForm do begin
        for i:=0 to count-1 do disp^[i].Y:=sqrt(disp^[i].Y)/npoints;
        AddData(TAcqData.CreateFromPoints(disp, count));
      end;
    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;
begin
  points:=nil;
  isbase:=nil;
  try
    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);
    pa := PExpPoints(points.Memory);
    BaseLine.Clear;

    {Find candidates for base}
    GetMem(isbase, cpt * sizeof(boolean));
    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
        newbaseln := TULBRObj(BaseLine.Add(ULBRID));
        {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 inc(ipt) 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));}
      end;
    end;
  finally
    if isbase <> nil then
      FreeMem(isbase,cpt*sizeof(boolean));
    if points <> nil then
      points.Free;
  end;
  Result := false;
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;

 procedure StoreNewPeak(PTX:TXValue);
 var
   pti,i:     integer;
   newpeak:   TULPRObj; { in ULPRType.PAS }
   MinX:      TXValue;
   PHHY,MinY: TYValue;
   PHHHit:    boolean;
   PHHLimX:   TXValue;
 begin
   pti:=XYFindXIndex(tmppa, dpt, PTX);
   {Reject small peaks under MinPeakHeight}
   if abs(tmppa^[pti].Y)<MinPeakHeight then exit;
   {Create new peak object}
   newpeak := TULPRObj(Peaks.Add(ULPRID));
   newpeak.X:= PTX;
   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;
  Peaks.DoChangeLock;
  try
    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);
    pa := PExpPoints(points.Memory);
    MinPeakWidth:=AcqData.ULM.MinPeakWidth;
    MinPeakHeight:=AcqData.ULM.MinPeakHeight;
    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 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);
var
  sp, dp: TExpPoint;
  i: TPointCount;
  y: TYValue;
begin
  sp.X := p.X1;
  if AcqData.FindDataPoint(sp, dp, i) then begin
    p.X1 := dp.X;
    p.Y1 := dp.Y;
    p.Index1 := i;
    if FindBaseY(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.X2, y) then
      p.BaseY2 := y
    else
      p.BaseY2 := dp.Y;
  end;
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);
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;

function TSpectrum.AutoDetect: boolean;
begin
  AutoDetectForBase;
  BaseLineUpdateFromMethod;
  AutoDetectForPeaks;
  Result := true;
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;
        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;
        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 > X) then begin
      nbl := bl;
      break;
    end else begin
      difx := xep.X - 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
          CurPeak := sp;
      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
            CurPeak := sp;
        end else begin
          CurPeak := sp;
        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);
  PeakAssignParamsFromMethod(p);
{  if FindBaseY(p.X1, y) then
    p.BaseY1 := y;
  if FindBaseY(p.X2, y) then
    p.BaseY2 := y;
}
end;

function TSpectrum.FindBaseY(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 (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 not PeakFindPeakInMethod(p, mp) then
      {/v0.10}
      begin
        mp := TULPRObj(AcqData.ULM_ULP.Add(ULPRID));
        mp.Assign(p);
      end;
    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;

procedure TSpectrum.PeakAssignParamsFromMethod(p: TULPRObj);
var mp: TULPRObj;
begin
  if PeakFindPeakInMethod(p, mp) then begin
    p.PeakName := mp.PeakName;
    p.GroupName := mp.GroupName;
  end;
end;

function TSpectrum.PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
begin
  Result := false;
  mp := nil;
  minmp := nil;
  mindif := MaxXValue;{ulantype}

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

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;}
  inherited Destroy;
end;
{/TSpectrum}

{******************** TAcqData *********************}

{TAcqData}
constructor TAcqData.Create(const AFileName: string; AMode: TOpenMode);
begin
  inherited Create;
  BufPointsClear;
  SpectrumOpt := DefSpectrumOpt;
  ClearScanInfo;
  {SetFilterPars(CurFilterPars);{from ulanglob}
  SetFileName(AFileName, AMode);
  {
  if not SetFileName(AFileName, AMode) then
    raise EDataOpenFailed.Create('Open ' + AFileName + ' failed.');}
end;

{v1.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;
*)
{/v1.05}

{v0.09}
constructor TAcqData.CreateFromPoints(APoints: PExpPoints; APointCount: integer);
var i: integer;
begin
  inherited Create;
  BufPointsClear;
  SpectrumOpt := DefSpectrumOpt;
  ClearScanInfo;
  FMode := omRead;
  ULF := TULFObj.Create(nil);
  CheckULRecords;
  ULAD.Data := TMemoryStream.Create;
  TMemoryStream(ULAD.Data).SetSize(APointCount * sizeof(TExpPoint));
  ULAD.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);
  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);
  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);
  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}

procedure TAcqData.SetFileName(const AFileName: string; AMode: TOpenMode);
var
  e: string;
  s: TFileStream;
  ei: TULEObj;
  defFound: boolean;
begin
  defFound:= false;
  FMode := AMode;
  BufPointsClear;
  ClearScanInfo;
  e := UpperCase(ExtractFileExt(AFileName));
  if FMode <> omCreate then begin
    if not FileExists(AFileName) then
      raise EFOpenError.Create('Can not open file ' + AFileName);
    ULF.Free;
    ULF := TULFObj.Create(nil);
    if (e = ULFExt) or (e = ASCExt) then
      ULF.LoadFromFile(AFileName);
    if FMode = omReadOnly then
      ULF.ReadOnly := true;
  end else begin
    if e <> ULFExt then
      raise EOnlyULFSupported.Create('TAcqData.SetFileName ' + AFileName);
    ULF.Free;
    ULF := TULFObj.Create(nil);
    if FileExists(DefaultAnalysisName) then begin
      ULF.LoadFromFile(DefaultAnalysisName);
      defFound := true;
    end else
      defFound := false;
  end;

  {v0.09}
  CheckULRecords;
  {/v0.09
  ULA := TULAObj(ULF.FindOrAdd(ULAID, ''));
    ULA.SetFlag(rfCantDelete, true);
  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);
  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);
  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);
  }
  if FMode = omCreate then begin
    if not defFound then
      ULF.SaveToFile(DefaultAnalysisName);
    ULF.FileName := AFileName;
    RawFileName := GetChannelFileName(DefPortName);
    try
      ULAD.Data := TFileStream.Create(RawFileName, fmCreate);
    except
      on EFCreateError do begin
        ShowMessage('Acquisition window already opened for ' + DefPortName, smError, 0);
        raise EAcqAlreadyOpened.Create('Acquisition window already opened for ' + DefPortName);
      end;
    end;
  end else begin
    if (e <> ULFExt) and (e <> AscExt) then begin
      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.Edit <> mrOK then
          raise EImportAborted.Create('Import aborted ' + AFileName);
        s := TFileStream.Create(AFileName, fmOpenRead);
        ULConvertFrom(ei, s, ULAD.Data);
        s.Free;
      end else if (e = GExt) or (e = BExt) then begin
        if not ULA.FindObj(ULEID, foFromRoot, BExt, TULObj(ei)) then
          ei := TULEObj(ULA.Add(ULEID));
        FillBExpImp(ei);
        s := TFileStream.Create(AFileName, fmOpenRead);
        ULConvertFrom(ei, s, ULAD.Data);
        s.Free;
      end else begin
        raise EInvalidFileExt.Create('Invalid file extension: ' + AFileName);
      end;
      ULF.FileName := ReplaceExt(AFileName, ULFExt, true);
    end;
    ScanData;
  end;
end;

procedure TAcqData.ScanData;
var ap:TExpPoint;
begin
  Reset;
  ClearScanInfo;
  begin
    while ReadPoint(ap) do
      CheckPoint(ap);
    Reset;
  end;
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
  {IsOn := true;}
  {if Stream <> NoHIO then}
  begin
    (*
    IOReset(Stream);{ Stream^.Reset; {iou}
    {v1.05}
    Seek(HeadSizeInFile);
    {/v1.05 IOSetPropInt(Stream, ipPos, 0);}
    *)
    ULAD.Data.Position := 0;
    ULAF.Data.Position := 0;{tfilestream}
    CurIndex := 0;
    BufPointsClear;
  end;
end;

procedure TAcqData.Seek(APos:longint);
begin
{  if Stream <> NoHIO then }
  begin
    ULAD.Data.Position := APos;
    ULAF.Data.Position := APos;
    {IOSetPropInt(Stream, ipPos, APos);}
    BufPointsClear;
  end;
end;

procedure TAcqData.SeekPoint(Index:Longint);
begin
(*  if Stream = NoHIO then
    exit;
  if IOGetPropInt(Stream, ipStatus) <> stOK then
    IOReset(Stream);
  Seek({v1.05}HeadSizeInFile + {/v1.05}Index * sizeof(TExpPoint));*)
  Seek(Index * sizeof(TExpPoint));
  CurIndex := Index;
end;

function TAcqData.WritePoint(var APoint:TExpPoint):boolean;
begin
  ULAD.Data.WriteBuffer(APoint, sizeof(APoint));
  Result := true;
  {
  WritePoint := false;
  if Stream <> NoHIO then begin
    WritePoint := (IOWrite(Stream, APoint, sizeof(TExpPoint)) = irOK);
  end;
  }
end;

function TAcqData.ReadPoint(var APoint:TExpPoint):boolean;
{var
 r:longint;
  ir:TIOResult;
  y:TYValue;
}
begin
  if UseFiltered then begin
    ReadPoint := (ULAF.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint));
  end else begin
    ReadPoint := (ULAD.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint));
  end;
(*
  ReadPoint := false;
  if Stream <> NoHIO then begin
    {v1.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 := ULAD.Data.Size;
{  GetSize := 0;
  if Stream <> NoHIO then
    GetSize := IOGetPropInt(Stream, ipSize);}
end;

function TAcqData.GetPointCount:longint;
{var l:longint;}
begin
  Result := GetSize div sizeof(TExpPoint);
{  l := GetSize;
  if l > HeadSizeInFile then
    l := (l - HeadSizeInFile) div sizeof(TExpPoint);
  GetPointCount := l;}
end;

procedure TAcqData.Clear;
begin
  Reset;
  ULAD.Data.Size := 0;
  ULAF.Data.Size := 0;
{
  if Stream <> NoHIO then
    IOSetPropInt(Stream, ipTruncate, 0);
}
end;

procedure TAcqData.Error(Code,Info:integer);
begin
  case Code of
    coIndexError:
      ShowMessage('Collection index out of range (TAcqData)'
        , smError, 0);
    coOverflow:
      ShowMessage('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 := ULAD.Data;
  if (Index < 0) or (s.Size < (Index * sizeof(TExpPoint))) then
    exit;
  s.Position := Index * sizeof(TExpPoint);
  s.ReadBuffer(APoint, sizeof(TExpPoint));
  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);
begin
  ULAD.Data.Position := Index * SizeOf(APoint);{, soFromBeggining)};
  ULAD.Data.WriteBuffer(APoint, sizeof(APoint));
{  if Stream <> NoHIO then begin
    Seek(HeadSizeInFile + Index * sizeof(TExpPoint));
    IOWrite(Stream, APoint, sizeof(TExpPoint));
  end;}
end;

procedure TAcqData.AddPoint(var APoint: TExpPoint);
begin
  FixPoint(APoint);
  CheckPoint(APoint);
  PutPoint(ULAD.Data.Size div sizeof(TExpPoint), APoint);

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

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));
  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) then begin
    ULF.SaveToFile(AFileName);
  end else begin
    raise EInvalidFileExt.Create('Invalid file extension: ' + AFileName);
  end;
end;

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 := ULAD.Data.Position div sizeof(TExpPoint);
end;

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

procedure TAcqData.SetViewLimit(const AViewLimit:TExpViewLimit);
begin
  SpectrumOpt.ViewLimit := AViewLimit;
  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('Zero  spectrum ViewLimit size', smError, 0);
  end;
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('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;
begin
  try
    if (ULF <> nil) and (ULF.FileName <> '') {v0.09} and ULF.Modified {/v0.09} then begin
      ULF.SaveToFile('');
      if RawFileName <> '' then begin
        ULAD.Data := nil;
        DeleteFile(RawFileName);
      end;
    end;
  finally
    ULF.Free;
    {isOn := false;}
  end;
  inherited Destroy;
end;

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

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

{/v1.05}
{/TAcqData}

{
procedure InitPacketRec(const ApexPacket:TApexPacket; var ApexRec:TApexPacketRec);
begin
  ApexRec.ZeroHead := @ApexPacket.Data[ApexZeroHeadPos];
  ApexRec.IDHead := @ApexPacket.Data[ApexIDHeadPos];
  ApexRec.Values := @ApexPacket.Data[ApexValuesPos];
  ApexRec.Foot := @ApexPacket.Data[ApexFootPos];
end;
}
end.