; docformat = 'rst' ; ; NAME: ; WindowImage ; ; PURPOSE: ; Allows the user to interactively adjust image contrast by means of "windowing and ; leveling" the image. Move the cursor vertically in the window to adjust the image ; stretch "window". Move the cursor horizontally in the window to adjust the image ; "level". ; ;******************************************************************************************; ; ; ; Copyright (c) 2010, by Fanning Software Consulting, Inc. All rights reserved. ; ; ; ; Redistribution and use in source and binary forms, with or without ; ; modification, are permitted provided that the following conditions are met: ; ; ; ; * Redistributions of source code must retain the above copyright ; ; notice, this list of conditions and the following disclaimer. ; ; * Redistributions in binary form must reproduce the above copyright ; ; notice, this list of conditions and the following disclaimer in the ; ; documentation and/or other materials provided with the distribution. ; ; * Neither the name of Fanning Software Consulting, Inc. nor the names of its ; ; contributors may be used to endorse or promote products derived from this ; ; software without specific prior written permission. ; ; ; ; THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY ; ; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ; ; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT ; ; SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, ; ; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ; ; TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ; ; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ; ; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ; ; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ; ; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ; ;******************************************************************************************; ; PRO WindowImage_OriginalSettings, event ; This event handler resotres the original window settings. ; Error handling. Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() IF N_Elements(info) NE 0 THEN $ Widget_Control, event.top, SET_UVALUE=info, /NO_COPY RETURN ENDIF ; Get the program information. Widget_Control, event.top, GET_UVALUE=info, /NO_COPY ; Restore original contrast and brightness values. contrast = 50 brightness = 50 level = (1-brightness/100.)*(info.maxImage - info.minImage) + info.minImage width = (1-contrast/100.)*(info.maxImage - info.minImage) ; Calculate the window size. displayMax = (level + (width / 2)) displayMin = (level - (width / 2)) IF displayMax GT info.maxImage THEN BEGIN difference = Abs(displayMax - info.maxImage) displayMax = displayMax - difference displayMin = displayMin - difference ENDIF IF displayMin LT info.minImage THEN BEGIN difference = Abs(info.minImage - displayMin) displayMin = displayMin + difference displayMax = displayMax + difference ENDIF ; Store everything. info.iWindow = [displayMin, displayMax] info.iLevel = level info.contrast = contrast info.brightness = brightness info.x = -1 info.y = -1 ; Display it in the window. WindowImage_Display, info Widget_Control, event.top, Set_UValue=info, /No_Copy END ;--------------------------------------------------------------------------- PRO WindowImage_Quit, event Widget_Control, event.top, /Destroy END ;--------------------------------------------------------------------------------------- PRO WindowImage_Resize, event ; Error handling. Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() IF N_Elements(info) NE 0 THEN $ Widget_Control, event.top, SET_UVALUE=info, /NO_COPY RETURN ENDIF ; Get the program information. Widget_Control, event.top, GET_UVALUE=info, /NO_COPY ; Calculate a new draw widget size. newx =info.labelxsize > event.x newy = event.y - info.labelysize - 86 ; Calculate a window size for the image. Maximum of 800 pixels. Minimum of 400. maximageSize = Max([newx, newy]) wsize = MaxWindowSize() IF info.imageAspect GE 1 THEN BEGIN maxSize = wsize[1] < (maxImageSize > 200) ywinsize = maxsize xwinsize = (maxsize / info.imageAspect) < wsize[0] ENDIF ELSE BEGIN maxSize = wsize[0] < (maxImageSize > 200) xwinsize = maxsize ywinsize = (maxsize * info.imageAspect) < (wsize[1] - info.labelysize - 86) ENDELSE ; Resize the draw widgets. Widget_Control, info.imgDrawID, DRAW_XSIZE=xwinSize, DRAW_YSIZE=ywinsize Widget_Control, info.cbDrawID, DRAW_XSIZE=xwinSize ; Redisplay the image. WindowImage_Display, info ; Put the program information back in storage. Widget_Control, event.top, SET_UVALUE=info, /NO_COPY END ;--------------------------------------------------------------------------------------- PRO WindowImage_DrawEvents, event ; Error handling. Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() IF N_Elements(info) NE 0 THEN $ Widget_Control, event.top, SET_UVALUE=info, /NO_COPY RETURN ENDIF ; What kind of event is this? possibleEvents = ['DOWN', 'UP', 'MOTION', 'VIEWPORT', 'EXPOSE'] thisEvent = possibleEvents[event.type] CASE thisEvent OF 'DOWN': BEGIN ; Get the program information. Widget_Control, event.top, GET_UVALUE=info, /NO_COPY ; Set the initial point. info.x = event.x info.y = event.y ; Clear events for this widget. Widget_Control, event.id, /CLEAR_EVENTS ; Turn motion events on. Widget_Control, event.ID, DRAW_MOTION_EVENTS=1 ; Put the program information back in storage and return. Widget_Control, event.top, SET_UVALUE=info, /NO_COPY RETURN END 'UP': BEGIN ; Get the program information. Widget_Control, event.top, GET_UVALUE=info, /NO_COPY ; Turn motion events off and clear any queued up events. Widget_Control, event.ID, DRAW_MOTION_EVENTS=0 Widget_Control, event.id, /CLEAR_EVENTS ; Calculate new contrast/brightness values. contrast = 0 > ((info.y - event.y) * info.cstep + info.contrast) < 99 brightness = 0 > ((info.x - event.x) * info.bstep + info.brightness) < 100 level = (1-brightness/100.)*(info.maxImage - info.minImage) + info.minImage width = (1-contrast/100.)*(info.maxImage - info.minImage) ; Calculate new display min/max. displayMax = (level + (width / 2)) displayMin = (level - (width / 2)) IF displayMax GT info.maxImage THEN BEGIN difference = Abs(displayMax - info.maxImage) displayMax = displayMax - difference displayMin = displayMin - difference ENDIF IF displayMin LT info.minImage THEN BEGIN difference = Abs(info.minImage - displayMin) displayMin = displayMin + difference displayMax = displayMax + difference ENDIF ; Store everything. info.iWindow = [displayMin, displayMax] info.iLevel = level info.contrast = contrast info.brightness = brightness info.x = event.x info.y = event.y ; Display the undated image. WindowImage_Display, info ; Put the program information back in storage and return. Widget_Control, event.top, SET_UVALUE=info, /NO_COPY END 'MOTION': BEGIN ; Get the program information. Widget_Control, event.top, GET_UVALUE=info, /NO_COPY ; Calculate new contrast/brightness values. contrast = 0 > ((info.y - event.y) * info.cstep + info.contrast) < 99 brightness = 0 > ((info.x - event.x) * info.bstep + info.brightness) < 100 level = (1-brightness/100.)*(info.maxImage - info.minImage) + info.minImage width = (1-contrast/100.)*(info.maxImage - info.minImage) ; Calculate new display min/max. displayMax = (level + (width / 2)) displayMin = (level - (width / 2)) IF displayMax GT info.maxImage THEN BEGIN difference = Abs(displayMax - info.maxImage) displayMax = displayMax - difference displayMin = displayMin - difference ENDIF IF displayMin LT info.minImage THEN BEGIN difference = Abs(info.minImage - displayMin) displayMin = displayMin + difference displayMax = displayMax + difference ENDIF ; Store the information. info.iWindow = [displayMin, displayMax] info.iLevel = level ; Update the image. WindowImage_Display, info ; Put the program information back in storage and return. Widget_Control, event.top, SET_UVALUE=info, /NO_COPY END ENDCASE END ;--------------------------------------------------------------------------------------- PRO WindowImage_Display, info cgSetColorState, 1, CURRENTSTATE=currentState ; Load the color table. cgLoadCT, info.colortable, REVERSE=info.reverse, BREWER=info.brewer, NCOLORS=253 TVLCT, cgColor(info.neutralColor, /TRIPLE), 254 ; Draw the image. WSet, info.imgWinID cgImage, info.image, SCALE=info.scale, NCOLORS=253, /Keep, $ MINVALUE=info.iwindow[0], MAXVALUE=info.iwindow[1], NOERASE=1 ; Draw the color bar. WSet, info.cbWinID cgErase cgColorbar, NCOLORS=253, CLAMP=info.iwindow, NEUTRALINDEX=254, $ POSITION=[0.05, 0.35, 0.95, 0.65], FONT=0, ANNOTATECOLOR='black', $ RANGE=[info.minImage, info.maxImage], DIVISIONS=5, FORMAT='(F0.2)' ; Write a label on the color bar. format = '(F0.3)' txt = 'Window: [' + String(info.iwindow[0], FORMAT=format) + ', ' + $ String(info.iwindow[1], FORMAT=format) + '] Level: ' + $ String(info.ilevel, FORMAT=format) cgText, 0.5, 0.75, /Normal, Alignment=0.5, Font=0, txt, Color='black' cgSetColorState, currentState END ;--------------------------------------------------------------------------------------- ;+ ; :Description: ; Allows the user to interactively adjust image contrast by means of "windowing and ; leveling" the image. Move the cursor vertically in the window to adjust the image ; stretch "window". Move the cursor horizontally in the window to adjust the image ; "level". ; ; :Categories: ; Graphics ; ; :Params: ; image: in, required, type=any ; Any 2D array that you wish to adjust the contrast of. ; ; :Keywords: ; brewer: in, optional, type=boolean, default=0 ; Set this keyword to indicate a Brewer color table is desired. ; colortable: in, optional, type=integer, default=0 ; The index number of a color table to load with cgLoadCT. ; neutralcolor: in, optional, type=string ; The name of the color to use for values outside the image "window" in ; the color table. If a default grayscale color table is loaded, the default ; color is "rose", otherwise the default is "black". ; reverse: in, optional, type=boolean, default=0 ; Set this keyword if you wish to reverse the color table. ; ; :Examples: ; To see a demonstation:: ; IDL> WindowImage ; ; :Author: ; FANNING SOFTWARE CONSULTING:: ; David W. Fanning ; 1645 Sheely Drive ; Fort Collins, CO 80526 USA ; Phone: 970-221-0438 ; E-mail: david@idlcoyote.com ; Coyote's Guide to IDL Programming: http://www.idlcoyote.com ; ; :History: ; Change History:: ; Written, 29 November 2010. DWF. ; Added color protection to the program. 30 Nov 2010. DWF. ; Modification of cgImage command to prevent flashing. 27 Feb 2011. DWF. ; ; :Copyright: ; Copyright (c) 2010, Fanning Software Consulting, Inc. ;- PRO WindowImage, image, $ BREWER=brewer, $ COLORTABLE=colortable, $ NEUTRALCOLOR=neutralColor, $ REVERSE=reverse Compile_Opt idl2 ; Error handling. Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() RETURN ENDIF ; Check parameters. IF N_Elements(image) EQ 0 THEN BEGIN filename = Filepath(Subdir=['examples','data'], 'muscle.jpg') Read_JPEG, filename, image ENDIF minImage = Min(image, MAX=maxImage, /NAN) IF (minImage LT 0) OR (maxImage GT 253) THEN scale = 1 ELSE scale = 0 brewer = Keyword_Set(brewer) reverse = Keyword_Set(brewer) IF N_Elements(colortable) EQ 0 THEN BEGIN IF N_Elements(neutralColor) EQ 0 THEN neutralColor = 'rose' IF brewer THEN BEGIN cgLoadCT, 17, REVERSE=reverse, BREWER=1, NCOLORS=253 colortable = 17 ENDIF ELSE BEGIN cgLoadCT, 0, REVERSE=reverse, BREWER=0, NCOLORS=253 colortable = 0 ENDELSE ENDIF ELSE BEGIN IF N_Elements(neutralColor) EQ 0 THEN neutralColor = 'gray' cgLoadCT, colortable, REVERSE=reverse, BREWER=brewer, NCOLORS=253 ENDELSE TVLCT, cgColor(neutralColor, /TRIPLE), 254 IF N_Elements(ilevel) EQ 0 THEN ilevel = (maxImage - minImage) / 2.0 IF N_Elements(iwindow) EQ 0 THEN BEGIN w20 = (maxImage - minImage) * 0.20 iwindow = minImage > [ilevel - w20, ilevel + w20] < maxImage ENDIF IF (iLevel LT iwindow[0]) OR (iLevel GT iwindow[1]) THEN BEGIN ilevel = (iwindow[1] - iwindow[0]) / 2.0 ENDIF ; Check image, get dimensions. IF Size(image, N_DIMENSIONS=1) NE 2 THEN Message, 'The image argument must be 2D.' s = Size(image, /DIMENSIONS) xsize = s[0] ysize = s[1] imageAspect = Float(s[1])/s[0] ; Calculate a window size for the image. Maximum of 800 pixels. Minimum of 400. maximageSize = Max([xsize, ysize]) maxSize = 800 < (maxImageSize > 400) IF imageAspect GE 1 THEN BEGIN ywinsize = maxsize xwinsize = maxsize / imageAspect ENDIF ELSE BEGIN xwinsize = maxsize ywinsize = maxsize * imageAspect ENDELSE ; Create widgets. tlb = Widget_Base(Title='Image Window/Leveling', Column=1, MBar=menuID, $ Base_Align_Center=1, TLB_Size_Events=1, YPAD=0) fileID = Widget_Button(menuID, Value='File') button = Widget_Button(fileID, Value='Restore Original Settings', $ Event_Pro='WindowImage_OriginalSettings') quitID = Widget_Button(fileID, Value='Quit', Event_Pro="WindowImage_Quit", /Separator) cbDrawID = Widget_Draw(tlb, XSize=xwinsize, YSize=80) imgDrawID = Widget_Draw(tlb, XSize=xwinsize, YSize=ywinsize, Button_Events=1, $ Event_Pro='WindowImage_DrawEvents') labelID = Widget_Label(tlb, Value='Drag cursor horizontally for BRIGHTNESS, ' + $ 'vertically for CONTRAST.', /Dynamic_Resize) Widget_Control, tlb, /REALIZE ; Get window index numbers. Widget_Control, cbDrawID, Get_Value=cbWinID Widget_Control, imgDrawID, Get_Value=imgWinID ; Find the Y screen sizes of the label widget. g = Widget_Info(labelID, /Geometry) labelysize = g.scr_ysize labelxsize = g.scr_xsize ; Create an info structure to hold program information. info = { image: image, $ xsize: xsize, $ ysize: ysize, $ labelxsize: labelxsize, $ labelysize: labelysize, $ iWindow: iWindow, $ iLevel: iLevel, $ cbWinID: cbWinID, $ imgWinID: imgWinID, $ scale: scale, $ colortable: colortable, $ neutralcolor: neutralcolor, $ brewer: brewer, $ reverse: reverse, $ x: -1, $ y: -1, $ imageAspect: imageAspect, $ imgDrawID: imgDrawID, $ cbDrawID: cbDrawID, $ contrast: 50, $ brightness: 50, $ cstep: ysize / 512., $ bstep: xsize / 2048., $ minImage: minImage, $ maxImage: maxImage } ; Display the initial window contents. WindowImage_Display, info Widget_Control, tlb, SET_UVALUE=info, /NO_COPY XManager, 'windowimage', tlb, /NO_BLOCK, EVENT_HANDLER='WindowImage_Resize' END ;---------------------------------------------------------------------------------------