unit Registration; {************************************************************************} {* Registration.p *} {* by Michael Castle *} {* University of Michigan Mental Health Research Institute (MHRI) *} {* e-mail address: mike.castle@umich.edu *} {* last modified on 11 March 1994 *} {************************************************************************} interface uses Types, Memory, QuickDraw, QuickDrawText, Packages, Menus, Events, Fonts, Scrap, ToolUtils, Resources, Errors, Palettes, StandardFile, Windows, OSUtils, Controls, TextEdit, Files, Dialogs, TextUtils, Finder, MixedMode, Processes, Globals, Utilities, File2, File1, Graphics, Camera, Text, Filters, Stacks; procedure DoRegister; implementation const RegisterImagesID = 129; {Dialog IDs} FiducialsOnScreenID = 3; FiducialsFromFileID = 4; ConfirmFidClicksID = 5; biggestFid = 9999; {maximum allowable fiducial stage coordinate} MaxFids = 12; {maximum number of fiducial points per slice} MaxRegSlices = 250; type RegisterRealArray = array[1..MaxRegSlices] of real; RealPoint = record x: real; y: real; end; {record} FidArray = array[1..MaxRegSlices, 1..MaxFids] of RealPoint; {******************************************************************************} {* RotateAboutXY rotates the point (x,y) counterclockwise by 'angle' radians about the point (xcenter, *} {* ycenter). *} {******************************************************************************} procedure RotateAboutXY (var x, y: extended; angle: extended; xcenter, ycenter: extended); var x0, y0: extended; SinAngle, CosAngle: extended; begin x0 := x; y0 := y; CosAngle := cos(angle); SinAngle := sin(angle); x := (x0 - xcenter) * CosAngle - (y0 - ycenter) * SinAngle + xcenter; y := (y0 - ycenter) * CosAngle + (x0 - xcenter) * SinAngle + ycenter; end; {RotateAboutXY} {******************************************************************************} {* Read from a file the fiducial data necessary to register a set of images. The data file contains several lines *} {* of coordinates delimited by tabs (x-coordinate followed by y-coordinate in all cases). The first line of the *} {* file should hold the coordinates of the Image Center point, the location (in screen coordinates) of the *} {* microscope crosshairs as they appear on the screen during image capture. The second line of the file should *} {* give the location in screen coordinates of two fixed points in an image (under the camera and microscope *} {* conditions selected for capture of the set of images to be aligned). The third line of the file should provide *} {* the location of these two fixed points in microscope stage coordinates. Each subsequent line in the file *} {* should contain (in stage coordinates) the locations of the Image Center and at least two fiducial points for an *} {* image in the set to be registered. Each image must be represented by the same number of fiducial points in *} {* the data file. Where fiducial coordinates are unavailable, coordinates of biggestFid+1 should appear. No *} {* more than MaxFids fiducial points are allowed for each image. *} {******************************************************************************} function GetFiducialDataFromFile (var xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2: integer; var xstage1, ystage1, xstage2, ystage2: extended; var fiducials: FidArray; var xc, yc: RegisterRealArray): boolean; var fiducialfname, str: str255; RefNum, nValues, i, j, nImages: integer; rLine: RealLine; begin nImages := info^.StackInfo^.nSlices; GetFiducialDataFromFile := FALSE; ShowMessage('Please open a fiducial data file.'); if not GetTextFile(fiducialfname, RefNum) then exit(GetFiducialDataFromFile); InitTextInput(fiducialfname, RefNum); GetLineFromText(rLine, nValues); if (nValues <> 2) then begin PutError('Expecting screen coordinates of Image Center point in line 1. Please edit fiducial data file and try again.'); exit(GetFiducialDataFromFile); end; xcscreen := round(rLine[1]); ycscreen := round(rLine[2]); GetLineFromText(rLine, nValues); if (nValues <> 4) then begin PutError('Expecting screen coordinates of two points in line 2. Please edit fiducial data file and try again.'); exit(GetFiducialDataFromFile); end; xscreen1 := round(rLine[1]); yscreen1 := round(rLine[2]); xscreen2 := round(rLine[3]); yscreen2 := round(rLine[4]); GetLineFromText(rLine, nValues); if (nValues <> 4) then begin PutError('Expecting stage coordinates of two points in line 3. Please edit fiducial data file and try again.'); exit(GetFiducialDataFromFile); end; xstage1 := rLine[1]; ystage1 := rLine[2]; xstage2 := rLine[3]; ystage2 := rLine[4]; i := 1; GetLineFromText(rLine, nValues); while (nvalues > 0) do begin if nValues >= 6 then begin for j := 1 to (nvalues - 2) div 2 do begin fiducials[i, j].x := rLine[j * 2 + 1]; fiducials[i, j].y := rLine[j * 2 + 2]; end; for j := (nvalues - 2) div 2 + 1 to MaxFids do begin fiducials[i, j].x := biggestFid + 1; fiducials[i, j].y := biggestFid + 1; end; {for j} xc[i] := rLine[1]; yc[i] := rLine[2]; end else begin str := StringOf('Expecting coordinates of image center and at least two fiducial points in line ', (i + 3) : 1, '. Please edit fiducial data file and try again.'); PutError(str); exit(GetFiducialDataFromFile); end; i := i + 1; GetLineFromText(rLine, nValues); end; {while} if (i < (nImages + 1)) then begin if (i = nImages) then str := StringOf('Expecting fiducial data for one more image. Please edit fiducial data file, then try again. ') else str := StringOf('Expecting fiducial data for ', (nImages + 1 - i) : 1, ' more slices. Please edit fiducial data file, then try again. '); PutError(str); exit(GetFiducialDataFromFile); end; if (i > (nImages + 1)) then begin if (i = nImages + 2) then str := StringOf('Too much fiducial data. Please edit fiducial data file, then try again. ') else str := StringOf('Too much fiducial data. Please edit fiducial data file, then try again. '); PutError(str); exit(GetFiducialDataFromFile); end; GetFiducialDataFromFile := TRUE; end; {GetFiducialDataFromFile} {******************************************************************************} {* Read the coordinates of a fiducial point entered on the screen by clicking once with the mouse. Interpret *} {* a double-click to indicate that this is the last fiducial point for the current slice, a spacebar-click to *} {* to indicate that no valid fiducial exists corresponding to this fiducial in other slices (record BiggestFid+1 *} {* as value for each coordinate), an option-click to indicate that some data for this slice has been improperly *} {* and the user would like to re-enter all fiducials for this slice, and command-period to indicate that the *} {* user wishes to cancel image registration altogether, discarding all entered fiducials coordinates. *} {******************************************************************************} function GetNextFiducial (var Fidx, Fidy: integer; var done, redo: boolean): boolean; var pt1, pt2: point; sbdown, optdown, DoubleClick: boolean; MouseUpTime: LongInt; begin SetCursor(ToolCursor[SelectionTool]); GetNextFiducial := FALSE; repeat sbdown := SpaceBarDown; optdown := OptionKeyDown; SetPort(info^.wptr); GetMouse(pt1); Show3Values(pt1.h, pt1.v, MyGetPixel(pt1.h, pt1.v)); if CommandPeriod then begin ShowMessage('Fiducial input cancelled.'); exit(GetNextFiducial); end; {then} until button; repeat until not (button); MouseUpTime := TickCount; DoubleClick := FALSE; repeat GetMouse(pt2); if EqualPt(pt1, pt2) then DoubleClick := button; until (TickCount - MouseUpTime > GetDblTime) or DoubleClick; if sbdown then begin Fidx := BiggestFid + 1; Fidy := BiggestFid + 1; end else if optdown then redo := TRUE else begin Fidx := pt1.h; Fidy := (Info^.nLines - 1) - pt1.v; end; done := DoubleClick; while (button) do {clear out any buffered mouse clicks; I don't know why there would be any} ; {such clicks, but they can be *very* disruptive while running on Quadras!} FlushEvents(62, 0); {make sure clicks and key presses don't linger in the event queue after exit} GetNextFiducial := TRUE; end; {GetNextFiducial} procedure SetSlice (i: integer); begin SelectSlice(i); Info^.StackInfo^.CurrentSlice := i; UpdateTitleBar; end; {******************************************************************************} {* Read fiducial coordinates for a set of slices to be placed in register with a reference slice (a member of *} {* the set). Begin by reading fiducials for the reference slice, then read the rest. Assign dummy values to *} {* variables in the fiducial data structure whose values would be used for mapping between two coordinate *} {* systems had fiducial data been read from a file. *} {******************************************************************************} function GetFiducialDataFromScreen (var xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2: integer; var xstage1, ystage1, xstage2, ystage2: extended; var fiducials: FidArray; var xc, yc: RegisterRealArray): boolean; var i, j: integer; nImages: integer; {number of slices} RefSlice: integer; {index of the reference slice} Fidx, Fidy: integer; {coordinates of selected fiducial point} done: boolean; {done entering fiducials for this slice} redo: boolean; {re-enter fiducials for this slice} str: str255; begin GetFiducialDataFromScreen := FALSE; nImages := Info^.StackInfo^.nSlices; xcscreen := 100; {arbitrarily assign dummy image center} ycscreen := 100; xscreen1 := 50; {assign screen and stage coords of two points} yscreen1 := 50; {so that mapping is 1:1 in x and y} xscreen2 := 80; yscreen2 := 80; xstage1 := 50.0; ystage1 := 50.0; xstage2 := 80.0; ystage2 := 80.0; DrawLabels('X: ', 'Y: ', 'Value: '); {prepare to show x,y values in results window} RefSlice := info^.StackInfo^.CurrentSlice; i := RefSlice; {begin with reference slice} while (i <= nImages) do begin done := FALSE; redo := FALSE; SetSlice(i); UpdatePicWindow; for j := 1 to MaxFids do begin if (not done) and (not redo) then begin str := StringOf('Click on fiducial point ', j : 1); showmessage(str); if not GetNextFiducial(Fidx, Fidy, done, redo) then exit(GetFiducialDataFromScreen); if ConfirmFidClicks then begin str := StringOf('Fiducial point ', j : 1, ': x = ', Fidx : 1, ' y = ', Fidy : 1); if not (redo) then while (PutMessageWithCancel(str) = cancel) do begin UpdatePicWindow; if not GetNextFiducial(Fidx, Fidy, done, redo) then exit(GetFiducialDataFromScreen); str := StringOf('Fiducial point ', j : 1, ': x = ', Fidx : 1, ' y = ', Fidy : 1); end; {while} UpdatePicWindow; end; {then} if done and (j = 1) then begin PutError('You must select at least two fiducial points in each slice for registration.'); redo := TRUE; end; {then} fiducials[i, j].x := Fidx; fiducials[i, j].y := Fidy; end {then} else begin {pad rest of array with invalid fiducial data} fiducials[i, j].x := biggestFid + 1; fiducials[i, j].y := biggestFid + 1; end; {else} end; {for j} xc[i] := 100; yc[i] := 100; if not (redo) then begin if (i = RefSlice) and (RefSlice <> 1) then i := 1 else if (i = RefSlice - 1) then {don't read fiducials from reference slice twice!} i := i + 2 else i := i + 1; end {then} else PutError('Input cancelled. Please reselect fiducial points for this slice.'); end; {while} GetFiducialDataFromScreen := TRUE; end; {GetFiducialDataFromScreen} {******************************************************************************} {* Before rotating and translating an image into register, center it in a larger buffer so that rotation does *} {* not unnecessarily clip portions of the image that will return to view after translation. *} {******************************************************************************} procedure CenterInBigBuffer (i, picwidth, picheight, bigpicwidth, bigpicheight: integer; StackInfo: InfoPtr); var vloc, hOffset, vOffset: integer; BigBufInfo: InfoPtr; aLine: LineType; begin BigBufInfo := info; info := StackInfo; SetSlice(i); info := BigBufInfo; SetForegroundColor(BlackIndex); SetBackgroundColor(WhiteIndex); SelectAll(false); DoOperation(EraseOp); {write image one line at a time to center of larger buffer} hOffset := (bigpicwidth - picwidth) div 2; vOffset := (bigpicheight - picheight) div 2; for vloc := 0 to picheight - 1 do begin info := StackInfo; GetLine(0, vloc, picwidth, aLine); info := BigBufInfo; PutLine(hOffset, vloc + vOffset, picwidth, aLine); end; UpdatePicWindow; end; {RegisterCenterInBigBuffer} {******************************************************************************} {* After registration is complete, move the centered image back to its original window size. *} {******************************************************************************} procedure TranslateBackToStack (i, xdelta, ydelta, picwidth, picheight, bigpicwidth, bigpicheight: longint; StackInfo: InfoPtr); var vloc, hoffset, voffset: integer; offset: longint; BigBufInfo: InfoPtr; aLine: LineType; begin BigBufInfo := info; info := StackInfo; SetSlice(i); hOffset := (bigpicwidth - picwidth) div 2 - xdelta; vOffset := (bigpicheight - picheight) div 2 + ydelta; for vloc := 0 to picheight - 1 do begin info := BigBufInfo; GetLine(hOffset, vloc + vOffset, picwidth, aLine); info := StackInfo; PutLine(0, vloc, picwidth, aLine); end; UpdatePicWindow; info := BigBufInfo; end; {RegisterBackToSmallWindow} {******************************************************************************} {* Find the angle which the current slice must be rotated in order to place it in register with the reference *} {* slice. For corresponding pairs of fiducial points in the current and reference slices, use simple *} {* trigonometry to find the angle between lines passing through each pair of points. Take an everage of the *} {* angles found from each set of corresponding pairs of points to find the rotation angle. *} {******************************************************************************} function RegisterFindAngle (var fiducials: FidArray; Cur, Ref: integer): extended; var j, k, n: integer; angle, anglecur, angleref: extended; tancur, tanref, bfid: extended; sumangle: extended; b:array[1..9] of boolean; begin {find average angle between current fiducial segments and reference fiducial segments} sumangle := 0; n := 0; for j := 1 to MaxFids - 1 do for k := j + 1 to MaxFids do begin bfid:=biggestFid; {ppc-bug} if (j <> k) and (fiducials[Cur, j].x < bfid) and (fiducials[Cur, j].y < bfid) and (fiducials[Cur, k].x < bfid) and (fiducials[Cur, k].y < bfid) and (fiducials[Ref, j].x < bfid) and (fiducials[Ref, j].y < bfid) and (fiducials[Ref, k].x < bfid) and (fiducials[Ref, k].y < bfid) then begin tanref := (fiducials[Ref, k].y - fiducials[Ref, j].y) / (fiducials[Ref, k].x - fiducials[Ref, j].x); if ((tanref > 0) and (fiducials[Ref, k].y - fiducials[Ref, j].y < 0)) or ((tanref < 0) and (fiducials[Ref, k].y - fiducials[Ref, j].y > 0)) then angleref := arctan(tanref) + pi else angleref := arctan(tanref); tancur := (fiducials[Cur, k].y - fiducials[Cur, j].y) / (fiducials[Cur, k].x - fiducials[Cur, j].x); if ((tancur > 0) and (fiducials[Cur, k].y - fiducials[Cur, j].y < 0)) or ((tancur < 0) and (fiducials[Cur, k].y - fiducials[Cur, j].y > 0)) then anglecur := arctan(tancur) + pi else anglecur := arctan(tancur); angle := anglecur - angleref; if (angle > pi) then angle := angle - 2 * pi; if (angle <= -pi) then angle := angle + 2 * pi; sumangle := sumangle + angle; n := n + 1; end; {then} end; if (n > 0) then RegisterFindAngle := sumangle / n else begin PutError('Insufficient fiducial data to calculate registration rotation.'); RegisterFindAngle := 10000; end; {else} end; {function RegisterFindAngle} {*************************************} {* Rotates the slice using ScaleAndRotate routine. *} {*************************************} procedure RegisterRotate (AngleInRadians: extended); begin rsHScale := 1.0; rsVScale := 1.0; rsAngle := (AngleInRadians / pi) * 180.0; if info^.LutMode = ColorLut then rsMethod := NearestNeighbor else rsMethod := Bilinear; rsCreateNewWindow := false; macro := true; {So ScaleAndRotate won't display its dialog box.} ScaleAndRotate; Macro := false; end; {******************************************************************************} {* Find the distances in x and y which the current slice must be translated in order to place it in register *} {* with the reference slice. *} {******************************************************************************} procedure FindTranslation (var xdelta, ydelta: integer; var fiducials: FidArray; i, RefPic: integer; xxscale, yyscale, angle: extended; xc, yc: RegisterRealArray; xcenterStage, ycenterStage: extended); var xcur, ycur: extended; xdeltaindex: extended; {used to detect non-linear mapping between coordinate systems of current} ydeltaindex: extended; { and reference slices; the closer to zero, the more linear the mapping} xdeltamin, ydeltamin: extended; {minimize indices to find best fiducials for translation calculations} j: integer; bfid: extended; begin xdeltamin := biggestFid; ydeltamin := biggestFid; bfid := biggestFid; {ppc-bug} for j := 1 to MaxFids do if (fiducials[i, j].x < bfid) and (fiducials[i, j].y < bfid) then begin xcur := fiducials[i, j].x - xc[i]; ycur := fiducials[i, j].y - yc[i]; {rotate original fiducials about screen center} {this changes values of first two parameters on return} RotateAboutXY(xcur, ycur, -angle, xcenterStage, ycenterStage); {calculate translation offsets} xdeltaindex := abs(fiducials[i, j].x - xc[i]) + abs(fiducials[RefPic, j].x - xc[RefPic]); if (xdeltaindex < xdeltamin) then begin {try to minimize effect of warped tissue} xdelta := round(xxscale * (fiducials[RefPic, j].x - xc[RefPic] - xcur)); xdeltamin := xdeltaindex; end; {then} ydeltaindex := abs(fiducials[i, j].y - yc[i]) + abs(fiducials[RefPic, j].y - yc[RefPic]); if (ydeltaindex < ydeltamin) then begin {try to minimize effect of warped tissue} ydelta := round(yyscale * (fiducials[RefPic, j].y - yc[RefPic] - ycur)); ydeltamin := ydeltaindex; end; {then} end; {then} end; {procedure RegisterFindTranslation} {******************************************************************************} {* This procedure allows the user to determine, via radio buttons in a dialog box, whether fiducial data *} {* will be read from the screen or from a file. The dialog box also contains details about how to select fiducial *} {* points on the screen. *} {******************************************************************************} function RegisterOptions: boolean; var mylog: Dialogptr; {pointer to dialog box} i, item, alldone: integer; SaveFiducialMethod: FiducialMethodType; SaveConfirmFidClicks: boolean; begin RegisterOptions := FALSE; InitCursor; SaveConfirmFidClicks := ConfirmFidClicks; SaveFiducialMethod := FiducialMethod; mylog := GetNewDialog(RegisterImagesID, nil, pointer(-1)); OutlineButton(MyLog, ok, 16); SetDlogItem(MyLog, FiducialsOnScreenID, ord(FiducialMethod = OnScreen)); SetDlogItem(MyLog, FiducialsFromFileID, ord(FiducialMethod = FromFile)); SetDlogItem(MyLog, ConfirmFidClicksID, ord(ConfirmFidClicks)); alldone := 0; repeat {if we don't do it this way, ModalDialog throws us into code checking after each keystroke} repeat {meaning you can't type in a 2 digit number} ModalDialog(nil, item); if item = ConfirmFidClicksID then begin ConfirmFidClicks := not ConfirmFidClicks; SetDlogItem(MyLog, ConfirmFidClicksID, ord(ConfirmFidClicks)); end; if (item = FiducialsOnScreenID) or (item = FiducialsFromFileID) then begin case item of FiducialsOnScreenID: FiducialMethod := OnScreen; FiducialsFromFileID: FiducialMethod := FromFile; end; {case} SetDlogItem(MyLog, FiducialsOnScreenID, ord(FiducialMethod = OnScreen)); SetDlogItem(MyLog, FiducialsFromFileID, ord(FiducialMethod = FromFile)); end; until (item = ok) or (item = cancel); alldone := 1; until (alldone = 1); DisposeDialog(mylog); if item = cancel then begin {if Cancel, keep the saved values} FiducialMethod := SaveFiducialMethod; ConfirmFidClicks := SaveConfirmFidClicks; Exit(RegisterOptions); end; RegisterOptions := TRUE; end; {******************************************************************************} {* Place a set of slices in register with a reference slice using fiducial marks. All slices in the stack *} {* are placed in register with the current slice using fiducial data gathered either from a text file or *} {* from the user's mouse clicks on the screen. *} {******************************************************************************} procedure DoRegister; var nImages: integer; {total number of open slices to register} RefImage: integer; {the index of the reference slice} bigpicwidth, bigpicheight: longint; {width,height of big buffer used for rotation and translation} picwidth, picheight: integer; {width,height of slices to register} xcenter, ycenter: integer; {coordinates of center of big, temp window} xdelta, ydelta: integer; {translation offsets} xcscreen, ycscreen: integer; {image center on the screen} xscreen1, yscreen1, xscreen2, yscreen2: integer;{two points in screen coordinates} xstage1, ystage1, xstage2, ystage2: extended; {same two points in stage coordinates} xxscale, yyscale: extended; {used for mapping stage to screen coords} xc, yc: RegisterRealArray; {array of image centers in stage coords} fiducials: FidArray; {array of fiducial point data for all slices} xcenterStage, ycenterStage: extended; {used in translation calculation} angle: extended; {mean angle between ref and cur fid segments} i, ignore: integer; {loop indices and temp var} TimeStr, seconds: str255; StartTicks, TicksForOneRegistration, TicksToGo: LongInt; StackInfo: InfoPtr; SlicesDone: integer; begin if not (RegisterOptions) then exit(DoRegister); with Info^ do begin picwidth := PixelsPerLine; picheight := nLines; RefImage := StackInfo^.CurrentSlice; nImages := StackInfo^.nSlices; end; if nImages > MaxRegSlices then begin PutError(concat('Unable to register more than ', long2str(MaxRegSlices), ' slices.')); exit(DoRegister); end; StackInfo := info; bigpicwidth := 2 * (round(1.414 * picwidth) div 2); {allow for image rotation without losing corners} bigpicheight := 2 * (round(1.414 * picheight) div 2); {odd window dims mysteriously don't work} if (bigpicwidth * bigpicheight) > UndoBufSize then begin PutError(concat('To register this stack, the size of the Undo buffer must be increased to at least ', long2str(bigpicwidth * bigpicheight div 1024), 'K.')); exit(DoRegister); end; xcenter := bigpicwidth div 2; {find center} ycenter := bigpicheight div 2; {open fiducial data file} {read fiducial marks and image centers into arrays; RegPic is image reference} {get screen to stage coordinate mapping} case FiducialMethod of OnScreen: if not GetFiducialDataFromScreen(xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2, xstage1, ystage1, xstage2, ystage2, fiducials, xc, yc) then exit(DoRegister); FromFile: if not GetFiducialDataFromFile(xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2, xstage1, ystage1, xstage2, ystage2, fiducials, xc, yc) then exit(DoRegister) end; {case} xxscale := (xscreen2 - xscreen1) / (xstage2 - xstage1); yyscale := (yscreen2 - yscreen1) / (ystage2 - ystage1); xcscreen := xcscreen + (bigpicwidth - picwidth) div 2; {adjust for bigger window} ycscreen := ycscreen + (bigpicheight - picheight) div 2; xcenterStage := (xcenter - xcscreen) / xxscale; ycenterStage := (ycenter - ycscreen) / yyscale; UpdatePicWindow; ShowWatch; i := 1; if not NewPicWindow('Temp', bigpicwidth, bigpicheight) then exit(DoRegister); ShowMessage(CmdPeriodToStop); SlicesDone := 1; {Don't need to process reference slice} while (i <= nImages) and not CommandPeriod do begin if i = RefImage then begin i := i + 1; if i > nImages then leave; end; StartTicks := TickCount; with info^ do SetWTitle(wptr, concat('Temp (', long2str(i), '/', long2str(nImages), ')')); {rotate image then translate to complete registration} angle := RegisterFindAngle(fiducials, i, RefImage); if (angle > 9999) then leave; CenterInBigBuffer(i, picwidth, picheight, bigpicwidth, bigpicheight, StackInfo); if (abs(angle) > 0.0001) then RegisterRotate(angle); if CommandPeriod then leave; FindTranslation(xdelta, ydelta, fiducials, i, RefImage, xxscale, yyscale, angle, xc, yc, xcenterStage, ycenterStage); TranslateBackToStack(i, xdelta, ydelta, picwidth, picheight, bigpicwidth, bigpicheight, StackInfo); SlicesDone := SlicesDone + 1; TicksForOneRegistration := TickCount - StartTicks; TicksToGo := (nImages - SlicesDone) * TicksForOneRegistration; NumToString((TicksToGo div 60) mod 60, seconds); if length(seconds) = 1 then seconds := concat('0', seconds); timestr := concat(long2str(TicksToGo div 3600), ':', seconds); ShowMessage(concat(CmdPeriodToStop, crStr, 'time: ', timestr)); i := i + 1; end; {while i} Info^.changes := false; ignore := CloseAWindow(info^.wptr); info := StackInfo; if CommandPeriod then ShowMessage('Registration cancelled.') else begin SetSlice(RefImage); {select registered slice as current slice} UpdatePicWindow; info^.changes := true; ShowMessage('Registration successful.'); end; {else} end; end.