Appendix A

The source to the main section of the program is contained in this appendix. The complete program also has a routine to load a LumiscanTM image into memory (see appendix B) and the bitmaps files containing the graphics used for button labels (see appendix C).

<sandra.pro>

;======================================================================
;
; Sandra -- a portal image processing system written in IDL
;
; Written by Jonathan Buzzard. (last update 12/08/96)
;
;    S - System for
;    A - Analysing
;    N - Normal
;    D - Displacements in
;    R - Radiotherapy
;    A - Alignment
;
;======================================================================


;
; The next three routines take care of the events being dispatched by
; XMANAGER, performing all the necessary actions.
;

;
; Handle the events generated by the main application
;
PRO SandraEventHdlr, event

	WIDGET_CONTROL, GET_UVALUE=control, event.id

	IF (STRPOS(control, "LEFT") EQ 0) THEN BEGIN
		iWhich = 1
		sAction = STRMID(control, 4, STRLEN(control)-4)
		control = "ACTION"
	ENDIF
	IF (STRPOS(control, "RIGHT") EQ 0) THEN BEGIN
		iWhich = 2
		sAction = STRMID(control, 5, STRLEN(control)-5)
		control = "ACTION"
	ENDIF

	CASE control OF

		"ABOUT": BEGIN
			WIDGET_CONTROL, event.top, GET_UVALUE=State, /NO_COPY
			WIDGET_CONTROL, State.InfoText, SET_VALUE="System for Analysing"$
						+" Normal Displacements in Radiotherapy Alignment"$
						+"        Version 1.1 (09/08/1996)"
			WIDGET_CONTROL, event.top, SET_UVALUE=State, /NO_COPY
		ENDCASE

		"EXIT": WIDGET_CONTROL, event.top, /DESTROY

		"ACTION" : CALL_PROCEDURE, sAction+"_EVENT", event.top, iWhich

		"IMAGE_LEFT": BEGIN


			; Marking control points on the images
			IF event.press NE 0 THEN RETURN

			WIDGET_CONTROL, event.top, GET_UVALUE=State, /NO_COPY

			; if mouse press in the correct image add a control point
			IF (NOT (State.ControlPtCount AND 1)) THEN BEGIN
				WSET, State.LeftImgHdl
				PLOTS, event.x, event.y, /DEVICE, COLOR=!D.TABLE_SIZE-1, PSYM=6
				InfoTxt = STRING(FORMAT='("Fiducial Pair:",I2," Left Point:",I4,",",I4)',$
							1+State.ControlPtCount/2, event.x, event.y)
				WIDGET_CONTROL, State.InfoText, SET_VALUE=InfoTxt
				WIDGET_CONTROL, State.LeftCtrlPts, GET_UVALUE=leftpts
				leftpts = [leftpts, event.x, event.y]
				State.ControlPtCount = State.ControlPtCount+1
				WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=leftpts
			ENDIF ELSE BEGIN
				WIDGET_CONTROL, State.InfoText, SET_VALUE="Click in the right-hand"$
							+" image to select the corresponding point"
			ENDELSE

			WIDGET_CONTROL, event.top, SET_UVALUE=State, /NO_COPY
		ENDCASE

 		"IMAGE_RIGHT": BEGIN

 			; Marking control points on the images
			IF event.press NE 0 THEN RETURN
			WIDGET_CONTROL, event.top, GET_UVALUE=State, /NO_COPY

			; if mouse press in the correct image add a control point
			IF (State.ControlPtCount AND 1) THEN BEGIN
				WSET, State.RightImgHdl
				PLOTS, event.x, event.y, /DEVICE, COLOR=!D.TABLE_SIZE-1, PSYM=6
				InfoTxt = STRING(FORMAT='("Fiducial Pair:",I2," Right Point:",I4,",",I4)',$
							1+State.ControlPtCount/2, event.x, event.y)
				WIDGET_CONTROL, State.InfoText, SET_VALUE=InfoTxt
				WIDGET_CONTROL, State.RightCtrlPts, GET_UVALUE=rightpts
				rightpts = [rightpts, event.x, event.y]
				State.ControlPtCount = State.ControlPtCount+1
				WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=rightpts
			ENDIF ELSE BEGIN
				WIDGET_CONTROL, State.InfoText, SET_VALUE="Click in the left-hand"$
							+" image to select a primary fiducial point"
			ENDELSE

			WIDGET_CONTROL, event.top, SET_UVALUE=State, /NO_COPY
		ENDCASE

		ELSE: CALL_PROCEDURE, control+"_EVENT", event.top

	ENDCASE
END

;
; Handle the events generated by the histogram dialog
;
PRO HistEventHdlr, event

	WIDGET_CONTROL, GET_UVALUE=control, event.id

	CASE control OF

		"CANCEL": WIDGET_CONTROL, event.top, /DESTROY

		"EQUALIZE" : BEGIN
        	WIDGET_CONTROL, event.top, GET_UVALUE=State, /NO_COPY
 			WIDGET_CONTROL, State.ImageStore, GET_UVALUE=iImage

			; do the actual histogram equalization
			iImage = HIST_EQUAL(TEMPORARY(iImage), MINV=State.LowCut,$
								MAXV=State.HighCut)
			WSET, State.ImageDraw
			TVSCL, iImage

			WIDGET_CONTROL, State.ImageStore, SET_UVALUE=iImage
       		WIDGET_CONTROL, event.top, SET_UVALUE=State, /NO_COPY

			WIDGET_CONTROL, event.top, /DESTROY
		ENDCASE

		"SLIDE_LOW": BEGIN
        	WIDGET_CONTROL, event.top, GET_UVALUE=State, /NO_COPY
        	iNewCut = event.value
        	; stop low cutoff from becoming greater than high cutoff
        	IF (iNewCut GE State.HighCut) THEN BEGIN
        		iNewCut=State.HighCut-1
        		WIDGET_CONTROL, event.id, SET_VALUE=State.HighCut-1
        	ENDIF
        	bTemp = State.HistImage(iNewCut,*)
			State.HistImage(iNewCut,0:127) = 200
			State.HistImage(State.LowCut,*)=State.LowCutStore
			State.LowCut = iNewCut
			State.LowCutStore = bTemp
			WSET, State.HistDraw
			TV, State.HistImage
			WIDGET_CONTROL, event.top, SET_UVALUE=State, /NO_COPY
		ENDCASE

 		"SLIDE_HIGH": BEGIN
        	WIDGET_CONTROL, event.top, GET_UVALUE=State, /NO_COPY
        	iNewCut = event.value
        	; stop high cutoff from becoming less than low cutoff
        	IF (iNewCut LE State.LowCut) THEN BEGIN
        		iNewCut=State.LowCut+1
        		WIDGET_CONTROL, event.id, SET_VALUE=State.LowCut+1
        	ENDIF
        	bTemp = State.HistImage(iNewCut,*)
			State.HistImage(iNewCut,0:127) = 200
			State.HistImage(State.HighCut,*)=State.HighCutStore
			State.HighCut = iNewCut
			State.HighCutStore = bTemp
			WSET, State.HistDraw
			TV, State.HistImage
			WIDGET_CONTROL, event.top, SET_UVALUE=State, /NO_COPY
		ENDCASE

	ENDCASE
END

;
; Handle the events generated by the Fader dialog
;
PRO FaderEventHdlr, event

	WIDGET_CONTROL, GET_UVALUE=control, event.id

	CASE control OF

		"DISMISS": WIDGET_CONTROL, event.top, /DESTROY

		"SLIDE_FADE" : BEGIN
			WIDGET_CONTROL, event.top, /HOURGLASS
        	WIDGET_CONTROL, event.top, GET_UVALUE=State, /NO_COPY

			; get the two images
			WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iRightImage, /NO_COPY
			WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iLeftImage, /NO_COPY

			; combine the two images at the appropriate fading
			WSET, State.ImageDraw
			TVSCL, event.value*FIX(iRightImage(0:state.XSize-1,0:State.YSize-1))+$
				(8-event.value)*FIX(iLeftImage(0:state.XSize-1,0:State.YSize-1))

			; store the two images back in there user values
			WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iRightImage, /NO_COPY
			WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iLeftImage, /NO_COPY

       		WIDGET_CONTROL, event.top, SET_UVALUE=State, /NO_COPY
		ENDCASE
	ENDCASE

END

;
; Clean up when Sandra exits; ie. restore colour table
;
PRO CleanUpSandra, wSandraWindow

	; Get the color table saved in the window's user value
	WIDGET_CONTROL, wSandraWindow, GET_UVALUE=SandraState
 	TVLCT, SandraState.ColourTable

END

;
; calculate the affine transformation and apply it
;
PRO affine_event, child

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	; need same number of points in each window and at least 2 pairs
	IF (State.ControlPtCount LT 4) THEN BEGIN
		WIDGET_CONTROL, State.InfoText,$
			SET_VALUE="Need at least two fiducial points to calculate tranformation"
		WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY
		RETURN
	ENDIF
	IF (State.ControlPtCount AND 1) THEN BEGIN
		WIDGET_CONTROL, State.InfoText,$
			SET_VALUE="Need same number of fiducial points in each window"
		WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY
		RETURN
	ENDIF

	WIDGET_CONTROL, State.LeftCtrlPts, GET_UVALUE=leftpts, /NO_COPY
	WIDGET_CONTROL, State.RightCtrlPts, GET_UVALUE=rightpts, /NO_COPY

	; the transformation calculated is the one to bring the
	; right hand image into alignment with the left and image.
	; IT DOES NOT represent the changes to be made on the treatment or
	; simulator machines to bring the patient into the correct position.
	A = DBLARR(4,State.ControlPtCount)
	FOR iLoop=0,State.ControlPtCount-1,2 DO BEGIN
		a(0,iLoop) = DOUBLE(leftpts(2+iLoop))
		a(1,iLoop) = -1.0D*DOUBLE(leftpts(3+iLoop))
		a(2,iLoop) = 1.0D
		a(3,iLoop) = 0.0D
		a(0,iLoop+1) = DOUBLE(leftpts(3+iLoop))
		a(1,iLoop+1) = DOUBLE(leftpts(2+iLoop))
		a(2,iLoop+1) = 0.0D
		a(3,iLoop+1) = 1.0D
	ENDFOR

 	B = DBLARR(State.ControlPtCount)
	FOR iLoop=0, State.ControlPtCount-1 DO BEGIN
		b(iLoop) = DOUBLE(rightpts(2+iLoop))
	ENDFOR

	SVDC, A, w, u, v, /DOUBLE
	n = N_ELEMENTS(w)
	wp = DBLARR(n, n)
	FOR iLoop=0,n-1 DO $
		IF (ABS(w(iLoop)) GE 1.0D-7) THEN wp(iloop, iloop) = 1.0D/w(iloop)

	x = v ## wp ## TRANSPOSE(u) ## b

	; decompose the transformation into its seperate components.
	; ONLY the rotation represents the change needed to patient
	; setup. If you know the magnification of the two images you
	; can work out the vertical movement needed to the table.
	; If you know the coordinates of the beam centre (isocentre),
	; and the pixel size, you can workout the lateral and
	; longditude movements of the table to correct patient setup.
	radeg = -57.29577951D
	theta = ATAN(x(1),x(0))
	AffText1 = STRING(FORMAT='("Rotation: ",F8.4,"    Scale: ",F8.4)',$
						radeg*theta,SIN(theta)/x(1))
	AffText2 = STRING(FORMAT='("    Translation: x=",F6.2,"  y=",F6.2)',$
						x(2), x(3))
	WIDGET_CONTROL, State.InfoText, SET_VALUE=AffText1+Afftext2

	; generate the matrix holding the transformation polynomial
	p = [x(2), -x(1), x(0), 0.0D]
	q = [x(3), x(0), x(1), 0.0D]

	; transform the image using cubic interpolation
	WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iImage, /NO_COPY
	iImage = POLY_2D(TEMPORARY(iImage), p, q, 2, MISSING=0)
	WSET, State.RightImgHdl
	TVSCL, iImage
	WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iImage, /NO_COPY

	; transform the control points (first need to invert matrix)
	invtran = INVERT([[x(0),-x(1)], [x(1),x(0)]], /DOUBLE)
	FOR iLoop=0,State.ControlPtCount-1,2 DO BEGIN
		xx = rightpts(2+iLoop)
		yy = rightpts(3+iLoop)
		rightpts(2+iLoop) = xx*invtran(0)+yy*invtran(1)-x(2)
		rightpts(3+iLoop) = xx*invtran(2)+yy*invtran(3)-x(3)
    ENDFOR

	; redraw the control points (add left points for comparison)
	FOR iLoop=0,State.ControlPtCount-1,2 DO BEGIN
		PLOTS, rightpts(2+iLoop), rightpts(3+iLoop), /DEVICE,$
			COLOR=!D.TABLE_SIZE-1, PSYM=6
		PLOTS, leftpts(2+iLoop), leftpts(3+iLoop), /DEVICE,$
			COLOR=!D.TABLE_SIZE-1, PSYM=6
	ENDFOR

	WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=rightpts, /NO_COPY
	WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=leftpts, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

END

;
; Histogram equalise an image
;
PRO histequ_event, child, iWhich

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	IF (iWhich EQ 1) THEN BEGIN
		hImageDraw = State.LeftImgHdl
		hImageStore = State.LeftImgStore
	ENDIF ELSE BEGIN
		hImageDraw = State.RightImgHdl
		hImageStore = State.RightImgStore
	ENDELSE

	WIDGET_CONTROL, hImageStore, GET_UVALUE=iImage, /NO_COPY

	; generate the histogram as an image
	iHist = HISTOGRAM(iImage, BINSIZE=1)
	fFact = 127.0/FLOAT(MAX(iHist(1:254)))
	iHistImg = BYTARR(256,128)
	FOR iLoop=1,254 DO BEGIN
		iHistImg(iLoop,0:iHist(iLoop)*fFact)=255
	ENDFOR

	; put the information stored in user values back
	WIDGET_CONTROL, hImageStore, SET_UVALUE=iImage, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

	; create dialog box for histogram equalization
	wHistWindow = WIDGET_BASE(TITLE="Histogram Equalization")
	wHistBase = WIDGET_BASE(wHistWindow, /COLUMN)
	wHistDraw = WIDGET_DRAW(wHistBase, XSIZE=256, YSIZE=128, RETAIN=2)
	wHistLowLabel = WIDGET_LABEL(wHistBase, VALUE="Low cut-off point",$
								/ALIGN_LEFT)
	wHistLow = WIDGET_SLIDER(wHistBase, /SUPPRESS_VALUE, MINIMUM=0,$
							MAXIMUM=255, VALUE=0, UVALUE="SLIDE_LOW")
	wHistHighLabel = WIDGET_LABEL(wHistBase, VALUE="High cut-off point",$
								/ALIGN_LEFT)
	wHistHigh = WIDGET_SLIDER(wHistBase, /SUPPRESS_VALUE, MINIMUM=0,$
							MAXIMUM=255, VALUE=255, UVALUE="SLIDE_HIGH")
	wHistButtonBase = WIDGET_BASE(wHistBase, /ROW)
	wHistEqual = WIDGET_BUTTON(wHistButtonBase, VALUE="Equalize",$
								UVALUE="EQUALIZE")
	wHistCancel = WIDGET_BUTTON(wHistButtonBase, VALUE="Cancel",$
								UVALUE="CANCEL")

	; realize the histogram dialog box
	WIDGET_CONTROL, /REALIZE, wHistWindow

	; display the histogram
	WIDGET_CONTROL, wHistDraw, GET_VALUE=hHistDraw
	WSET, hHistDraw
	TV, iHistImg

	; store the histogram state inforamtion in user value of the dialog
 	HistState = CREATE_STRUCT('LowCut', 0)
 	HistState = CREATE_STRUCT(HistState, 'LowCutStore', BYTARR(128))
	HistState = CREATE_STRUCT(HistState, 'HighCut', 255)
	HistState = CREATE_STRUCT(HistState, 'HighCutStore', BYTARR(128))
	HistState = CREATE_STRUCT(HistState, 'HistImage', iHistImg)
	HistState = CREATE_STRUCT(HistState, 'HistDraw', hHistDraw)
	HistState = CREATE_STRUCT(HistState, 'ImageStore', hImageStore)
	HistState = CREATE_STRUCT(HistState, 'ImageDraw', hImageDraw)
	WIDGET_CONTROL, wHistWindow, SET_UVALUE=HistState, /NO_COPY

	; register dialog box with XMANAGER as modal
	XMANAGER, "Histogram", wHistWindow, EVENT_HANDLER="HistEventHdlr",$
			/MODAL

END

;
; Pop up a dialog box to fade between the two images
;
PRO fade_event, child

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iRightImage, /NO_COPY
	WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iLeftImage, /NO_COPY

	; find the sizes of the two images
	rightSize=SIZE(iRightImage)
	leftSize=SIZE(iLeftImage)

	; select the smaller x and y sizes for image addition
	IF (rightsize(1) LE leftsize(1)) THEN xx=rightsize(1) ELSE xx=leftsize(1)
	IF (rightsize(2) LE leftsize(2)) THEN yy=rightsize(2) ELSE yy=leftsize(2)

 	; create dialog box for fading between two images
	wFadeWindow = WIDGET_BASE(TITLE="Fader")
	wFadeBase = WIDGET_BASE(wFadeWindow, /COLUMN)
	wFadeDraw = WIDGET_DRAW(wFadeBase, XSIZE=xx, YSIZE=yy,$
							X_SCROLL_SIZE=400, Y_SCROLL_SIZE=400,$
							RETAIN=2, /SCROLL)
	wFadeControlBase = WIDGET_BASE(wFadeBase, /ROW)
	wFadeSlide = WIDGET_SLIDER(wFadeControlBase, MINIMUM=0,$
							MAXIMUM=8, VALUE=4, UVALUE="SLIDE_FADE")
	wFadeDismiss = WIDGET_BUTTON(wFadeControlBase, VALUE="Dismiss",$
								UVALUE="DISMISS")

	; realize the fader dialog box
	WIDGET_CONTROL, /REALIZE, wFadeWindow

	; display the two images equally mixed
	WIDGET_CONTROL, wFadeDraw, GET_VALUE=hFadeDraw
	WSET, hFadeDraw
	TVSCL, FIX(iRightImage)+FIX(iLeftImage(0:xx-1,0:yy-1))

	; store the fader state inforamtion in user value of the dialog
 	FadeState = CREATE_STRUCT('XSize', xx)
 	FadeState = CREATE_STRUCT(FadeState, 'YSize', yy)
 	FadeState = CREATE_STRUCT(FadeState, 'LeftImgStore', State.LeftImgStore)
	FadeState = CREATE_STRUCT(FadeState, 'RightImgStore', State.RightImgStore)
	FadeState = CREATE_STRUCT(FadeState, 'ImageDraw', hFadeDraw)
	WIDGET_CONTROL, wFadeWindow, SET_UVALUE=FadeState, /NO_COPY

	; restore the state inforamtion and images in their widget user values
	WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iRightImage, /NO_COPY
	WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iLeftImage, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

	; register dialog box with XMANAGER as modal
	XMANAGER, "Fader", wFadeWindow, EVENT_HANDLER="FaderEventHdlr",$
			/MODAL

END

;
; Clear the control points from screen and memory
;
PRO clear_pts_event, child

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	; redraw the left image
	WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iImage, /NO_COPY
	WSET, State.LeftImgHdl
	TVSCL, iImage
	WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iImage, /NO_COPY

	; redraw the right image
	WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iImage, /NO_COPY
	WSET, State.RightImgHdl
	TVSCL, iImage
	WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iImage, /NO_COPY

	; reset the control points
	WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=[-1, -1]
	WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=[-1, -1]
	State.ControlPtCount = 0

	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

END

;
; Load an image, resetting control points
;
PRO load_event, child, iWhich

	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY
	file = PICKFILE(FILE="", GROUP=child, FILTER="*.img", /READ)

	IF (file EQ "") THEN BEGIN
		WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY
		RETURN
	ENDIF

	WIDGET_CONTROL, child, /HOURGLASS

    ; redraw the other image as necessary
  	IF (iWhich EQ 1) THEN BEGIN
  		IF (State.ControlPtCount GT 0) THEN BEGIN
			WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.RightImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
		lImgWdg = State.LeftImgWdg
		hImageDraw =State.LeftImgHdl
		hImageStore = State.LeftImgStore
	ENDIF ELSE BEGIN
  		IF (State.ControlPtCount GT 0) THEN BEGIN
			WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.LeftImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
		lImgWdg = State.RightImgWdg
		hImageDraw =State.RightImgHdl
		hImageStore = State.RightImgStore
	ENDELSE

	; reset the control points
	WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=[-1, -1]
	WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=[-1, -1]
	State.ControlPtCount = 0

	read_lum, file, iImage
	iImage(WHERE(iImage GT 4094)) = 4094
	iImage = BYTSCL(TEMPORARY(iImage))

	iDim = SIZE(iImage)
	WIDGET_CONTROL, lImgWdg, XSIZE=iDim(1), YSIZE=iDim(2)

	; store the image size
  	IF (iWhich EQ 1) THEN BEGIN
		State.LeftImgX = iDim(1)
		State.LeftImgY = iDim(2)
	ENDIF ELSE BEGIN
		State.RightImgX = iDim(1)
		State.RightImgY = iDim(2)
	ENDELSE

	WSET, hImageDraw
	TVSCL, iImage

	WIDGET_CONTROL, hImageStore, SET_UVALUE=iImage, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

END

;
; Routine to rotate an image counter clockwise 90 degrees
;
PRO rotccw_event, child, iWhich

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	IF (iWhich EQ 1) THEN BEGIN
		lImgWdg = State.LeftImgWdg
		hImageDraw = State.LeftImgHdl
		hImageStore = State.LeftImgStore
		iTemp = State.LeftImgX
		State.LeftImgX = State.LeftImgY
		State.LeftImgY = iTemp
		WIDGET_CONTROL, lImgWdg, XSIZE=State.LeftImgX, YSIZE=State.LeftImgY
		IF (State.ControlPtCount GT 1) THEN BEGIN
			WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.RightImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDIF ELSE BEGIN
		lImgWdg = State.RightImgWdg
		hImageDraw = State.RightImgHdl
		hImageStore = State.RightImgStore
		iTemp = State.RightImgX
		State.RightImgX = State.RightImgY
		State.RightImgY = iTemp
		WIDGET_CONTROL, lImgWdg, XSIZE=State.RightImgX, YSIZE=State.RightImgY
		IF (State.ControlPtCount GT 0) THEN BEGIN
			WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.LeftImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDELSE

	WIDGET_CONTROL, hImageStore, GET_UVALUE=iImage, /NO_COPY

	iImage = ROTATE(TEMPORARY(iImage), 1)

	WSET, hImageDraw
	TVSCL, iImage

	; reset the control points
	WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=[-1, -1]
	WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=[-1, -1]
	State.ControlPtCount = 0

	WIDGET_CONTROL, hImageStore, SET_UVALUE=iImage, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

END

;
; Routine to rotate an image clockwise 90 degrees
;
PRO rotcw_event, child, iWhich

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	IF (iWhich EQ 1) THEN BEGIN
		lImgWdg = State.LeftImgWdg
		hImageDraw = State.LeftImgHdl
		hImageStore = State.LeftImgStore
		iTemp = State.LeftImgX
		State.LeftImgX = State.LeftImgY
		State.LeftImgY = iTemp
		WIDGET_CONTROL, lImgWdg, XSIZE=State.LeftImgX, YSIZE=State.LeftImgY
		IF (State.ControlPtCount GT 1) THEN BEGIN
			WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.RightImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDIF ELSE BEGIN
		lImgWdg = State.RightImgWdg
		hImageDraw = State.RightImgHdl
		hImageStore = State.RightImgStore
		iTemp = State.RightImgX
		State.RightImgX = State.RightImgY
		State.RightImgY = iTemp
		WIDGET_CONTROL, lImgWdg, XSIZE=State.RightImgX, YSIZE=State.RightImgY
		IF (State.ControlPtCount GT 0) THEN BEGIN
			WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.LeftImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDELSE

	WIDGET_CONTROL, hImageStore, GET_UVALUE=iImage, /NO_COPY

	iImage = ROTATE(TEMPORARY(iImage), 3)
	Dim = SIZE(iImage)
	WIDGET_CONTROL, lImgWdg, XSIZE=Dim(1), YSIZE=Dim(2)
	WSET, hImageDraw
	TVSCL, iImage

	; reset the control points
	WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=[-1, -1]
	WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=[-1, -1]
	State.ControlPtCount = 0

	WIDGET_CONTROL, hImageStore, SET_UVALUE=iImage, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

END

;
; Routine to flip the portal image horizontally
;
PRO fliph_event, child, iWhich

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	IF (iWhich EQ 1) THEN BEGIN
		hImageDraw = State.LeftImgHdl
		hImageStore = State.LeftImgStore
		IF (State.ControlPtCount GT 1) THEN BEGIN
			WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.RightImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDIF ELSE BEGIN
		hImageDraw = State.RightImgHdl
		hImageStore = State.RightImgStore
		IF (State.ControlPtCount GT 0) THEN BEGIN
			WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.LeftImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDELSE

	WIDGET_CONTROL, hImageStore, GET_UVALUE=iImage, /NO_COPY

	iImage = REVERSE(TEMPORARY(iImage),1)
	WSET, hImageDraw
	TVSCL, iImage

	; reset the control points
	WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=[-1, -1]
	WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=[-1, -1]
	State.ControlPtCount = 0

	WIDGET_CONTROL, hImageStore, SET_UVALUE=iImage, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

END

;
; Routine to flip the portal image vertically
;
PRO flipv_event, child, iWhich

	WIDGET_CONTROL, child, /HOURGLASS
	WIDGET_CONTROL, child, GET_UVALUE=State, /NO_COPY

	IF (iWhich EQ 1) THEN BEGIN
		hImageDraw = State.LeftImgHdl
		hImageStore = State.LeftImgStore
		IF (State.ControlPtCount GT 1) THEN BEGIN
			WIDGET_CONTROL, State.RightImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.RightImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.RightImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDIF ELSE BEGIN
		hImageDraw = State.RightImgHdl
		hImageStore = State.RightImgStore
		IF (State.ControlPtCount GT 0) THEN BEGIN
			WIDGET_CONTROL, State.LeftImgStore, GET_UVALUE=iImage, /NO_COPY
			WSET, State.LeftImgHdl
			TVSCL, iImage
			WIDGET_CONTROL, State.LeftImgStore, SET_UVALUE=iImage, /NO_COPY
		ENDIF
	ENDELSE

	WIDGET_CONTROL, hImageStore, GET_UVALUE=iImage, /NO_COPY

	iImage = REVERSE(TEMPORARY(iImage),2)
	WSET, hImageDraw
	TVSCL, iImage

	; reset the control points
	WIDGET_CONTROL, State.LeftCtrlPts, SET_UVALUE=[-1, -1]
	WIDGET_CONTROL, State.RightCtrlPts, SET_UVALUE=[-1, -1]
	State.ControlPtCount = 0

	WIDGET_CONTROL, hImageStore, SET_UVALUE=iImage, /NO_COPY
	WIDGET_CONTROL, child, SET_UVALUE=State, /NO_COPY

END

;
; Create the widgets that make up the point and click interface to SANDRA,
; and then register them with XMANAGER
;

PRO sandra

	; get the current colour vectors to restore when application is exited.
	TVLCT, savedR, savedG, savedB, /GET

	; build colour table from colour vectors
	colourTable = [[savedR],[savedG],[savedB]]

	; create the top level window for the application
	wSandraWindow = WIDGET_BASE(TITLE="Sandra", MBAR=wMenuBar)

	wFileMenu = WIDGET_BUTTON(wMenuBar, VALUE="File", /MENU)
	wOpen1Item = WIDGET_BUTTON(wFileMenu, VALUE="Open Left Image...",$
								UVALUE="LEFTLOAD")
	wOpen2Item = WIDGET_BUTTON(wFileMenu, VALUE="Open Right Image...",$
								UVALUE="RIGHTLOAD")
	wExitItem = WIDGET_BUTTON(wFileMenu, VALUE="Exit", UVALUE="EXIT")


	wEditMenu = WIDGET_BUTTON(wMenuBar, VALUE="Edit", /MENU)
	wClearPtItem = WIDGET_BUTTON(wEditMenu, VALUE="Clear Points",$
								UVALUE="CLEAR_PTS")
	wAffineItem = WIDGET_BUTTON(wEditMenu, VALUE="Affine Transform",$
								UVALUE="AFFINE")
	wFadeItem = WIDGET_BUTTON(wEditMenu, VALUE="Fade Images...",$
								UVALUE="FADE")

	wHelpMenu = WIDGET_BUTTON(wMenuBar, VALUE="Help", /HELP, /MENU)
	wHelpItem = WIDGET_BUTTON(wHelpMenu, VALUE="About", UVALUE="ABOUT")

	wSandraBase = WIDGET_BASE(wSandraWindow, /COLUMN)
	wDrawBase = WIDGET_BASE(wSandraBase, /ROW)
	wLeftBase = WIDGET_BASE(wDrawBase, /COLUMN)
	wRightBase = WIDGET_BASE(wDrawBase, /COLUMN)

	; Determine hardware display size.
	DEVICE, GET_SCREEN_SIZE = screenSize
	IF (screenSize(0) GT 640) THEN iScroll = 370 ELSE iScroll = 285

	; create the two draw widgets to hold the images
	wLeftDraw = WIDGET_DRAW(wLeftBase, XSIZE=512, YSIZE=512,$
							X_SCROLL_SIZE=iScroll, Y_SCROLL_SIZE=iScroll,$
							RETAIN=2, /BUTTON_EVENTS, /SCROLL,$
							UVALUE = "IMAGE_LEFT")
	wRightDraw = WIDGET_DRAW(wRightBase, XSIZE=512, YSIZE=512,$
							X_SCROLL_SIZE=iScroll, Y_SCROLL_SIZE=iScroll,$
							RETAIN=2, /BUTTON_EVENTS, /SCROLL,$
							UVALUE= "IMAGE_RIGHT")

	; load the bitmaps for the buttons
	READ_X11_BITMAP, "rotccw.xbm", bRotateCCW
	READ_X11_BITMAP, "rotcw.xbm", bRotateCW
	READ_X11_BITMAP, "fliph.xbm", bFlipH
	READ_X11_BITMAP, "flipv.xbm", bFlipV
 	READ_X11_BITMAP, "histoequ.xbm", bHistoEqu

	; create the left image buttons
	wLeftButtonBase = WIDGET_BASE(wLeftBase, /ROW)
	wLeftRotateCCW = WIDGET_BUTTON(wLeftButtonBase, VALUE=bRotateCCW,$
					UVALUE="LEFTROTCCW")
	wLeftRotateCW = WIDGET_BUTTON(wLeftButtonBase, VALUE=bRotateCW,$
					UVALUE="LEFTROTCW")
	wLeftFlipH = WIDGET_BUTTON(wLeftButtonBase, VALUE=bFlipH,$
					UVALUE="LEFTFLIPH")
	wLeftFlipV = WIDGET_BUTTON(wLeftButtonBase, VALUE=bFlipV,$
					UVALUE="LEFTFLIPV")
	wLeftHistEqu = WIDGET_BUTTON(wLeftButtonBase, VALUE=bHistoEqu,$
					UVALUE="LEFTHISTEQU")

	; now create the right image buttons
	wRightButtonBase = WIDGET_BASE(wRightBase, /ROW)
	wRightRotateCCW = WIDGET_BUTTON(wRightButtonBase, VALUE=bRotateCCW,$
					UVALUE="RIGHTROTCCW")
	wRightRotateCW = WIDGET_BUTTON(wRightButtonBase, VALUE=bRotateCW,$
					UVALUE="RIGHTROTCW")
	wRightFlipH = WIDGET_BUTTON(wRightButtonBase, VALUE=bFlipH,$
					UVALUE="RIGHTFLIPH")
	wRightFlipV = WIDGET_BUTTON(wRightButtonBase, VALUE=bFlipV,$
					UVALUE="RIGHTFLIPV")
	wRightHistEqu = WIDGET_BUTTON(wRightButtonBase, VALUE=bHistoEqu,$
					UVALUE="RIGHTHISTEQU")

    ; Create a text widget to display information
	wInfoText = WIDGET_TEXT(wSandraBase, XSIZE=32, YSIZE = 1,$
              VALUE=STRING(REPLICATE(32B,32)) )


	; realize the window
	WIDGET_CONTROL, /REALIZE, wSandraWindow

	WIDGET_CONTROL, wLeftDraw, GET_VALUE=hLeftDraw
	WIDGET_CONTROL, wRightDraw, GET_VALUE=hRightDraw

	; save the previous colour table in the user value to restore on exit
	SandraState = CREATE_STRUCT('ColourTable', colourTable)

	SandraState = CREATE_STRUCT(SandraState, 'LeftImgHdl', hLeftDraw)
	SandraState = CREATE_STRUCT(SandraState, 'LeftImgWdg', wLeftDraw)
	SandraState = CREATE_STRUCT(SandraState, 'LeftImgStore', wLeftBase)
	SandraState = CREATE_STRUCT(SandraState, 'LeftImgX',512)
	SandraState = CREATE_STRUCT(SandraState, 'LeftImgY',512)
	SandraState = CREATE_STRUCT(SandraState, 'RightImgHdl', hRightDraw)
	SandraState = CREATE_STRUCT(SandraState, 'RightImgWdg', wRightDraw)
	SandraState = CREATE_STRUCT(SandraState, 'RightImgStore', wRightBase)
	SandraState = CREATE_STRUCT(SandraState, 'RightImgX',512)
	SandraState = CREATE_STRUCT(SandraState, 'RightImgY',512)
	SandraState = CREATE_STRUCT(SandraState, 'RightCtrlPts', wRightButtonBase)
	SandraState = CREATE_STRUCT(SandraState, 'LeftCtrlPts', wLeftButtonBase)
	SandraState = CREATE_STRUCT(SandraState, 'ControlPtCount', 0)
	SandraState = CREATE_STRUCT(SandraState, 'InfoText', wInfoText)

	WIDGET_CONTROL, wLeftButtonBase, SET_UVALUE=[-1, -1]
	WIDGET_CONTROL, wRightButtonBase, SET_UVALUE=[-1, -1]
	LOADCT, 1  ; 0-greyscale 1-blue/white 3-hotbody

	; load two blank 512x512 images into draw widgets this means we
	; don't have to track whether images are loaded.
	WIDGET_CONTROL, wLeftBase, SET_UVALUE=BYTARR(512,512)
	WIDGET_CONTROL, wRightBase, SET_UVALUE=BYTARR(512,512)

	WIDGET_CONTROL, wSandraWindow, SET_UVALUE=SandraState, /NO_COPY

	; register the application with XMANAGER
	XMANAGER, "Sandra", wSandraWindow, EVENT_HANDLER="SandraEventHdlr",$
			CLEANUP="CleanUpSandra"

END


PREVIOUS   CONTENTS   NEXT