跳转至

Getting creative with ENVI + IDL

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/getting-creative-with-envi-idl

7584 Rate this article:

5.0

Getting creative with ENVI + IDL

Zachary Norman Friday, May 6, 2016

When it comes to ENVI + IDL, I like to get creative and see what else I can really accomplish with all of the awesome tools I have available. To give you some background on this blog, a few months ago I got my first DSLR camera. When I wanted to take pictures of larger scenes I realized that I didn't have the tools I needed to generate mosaics or panoramas of vast landscapes.

From a previous blog post, Creating Mosaics from Pluto Imagery, I had a very simple approach to reference images with respect to one another which was just setting a constant offset between each image. If you are taking pictures by hand, this is a little more difficult to do, so I turned to my great friend IDL for some help.

With IDL's function graphics you can create interactive graphics windows. In order to correctly position my images, this is what I decided to use because I can interactively move around different images and put them where they should be. Then, based on their final positions in the window, I can create spatial references for ENVI to have an idea where the images are relative to one another. This is important because if you want to use ENVI's Seamless Mosaic tool or Image Registration Workflow then you need to have spatial references for your images.

Here is a screenshot of what the interface looks like for the widget I created with some sample images:

In order to use the tool you will need to download the code (see below). Once you get the code and run the procedure, then a dialog will appear asking you to select some images you want to load. The allowed formats are any images that can be read by IDL's READ_IMAGE function. After you select your images, the widget interface above will appear with different actions you can perform on a selected image (the images selected when there is a blue box around it). To load an image into the display, you just need to double click on the name of the file in the top right corner of the image. If you get any of the images in a goofy position, orientation, or weird zoom, then just select the image and press the 'Reset Properties' button.

Once you are done, press the 'Done!' button. This will create ENVI formatted files for every image in the display. Once this has finished, then a prompt will appear asking if you want to open up the images in ENVI. If you select yes, then you will see something like the following after the images are loaded:

At this point, you might need to use the Image Registration Workflow to really get the images lined up better before using the Seamless Mosaic tool. Have fun mosaicing!

Here is the code for the widget that I call IDLMosaic. Feel free to edit/modify the code in any way you want. Copy and past the code into IDL and save it as "idlmosaic.pro"

;+

;  

;   Program designed to create an interactive environment for lining up images

;   relative to one another so that they can be georeferenced in ENVI. When you

;   press the run button in IDL, a dialog will pop up that prompts you to select

;   images that will be read into IDL. The supported file formats are the same

;   for the READ_IMAGE function in IDL.

;

; :Author: Zachary Norman

;-

function ImageInfo::init, oWin, MAX_WIDTH = max_width

compile_opt idl2

self.ImageHash = orderedhash()

self.oImages = list()

self.oFiles = list()

;check if a max width was supplied

if (max_width eq !NULL) then begin

self.max_width = .33d

endif else begin

self.max_width = double(max_width)

endelse

;check if a window reference was supplied, if not make one

if ~isa(oWin, 'GRAPHICSWIN')then begin

self.oWin = window()

endif else begin

self.oWin = oWin

endelse

return, 1

end

pro ImageInfo::AddImage, drawfile, CURRENT = current

compile_opt idl2

;overall hash with the image info

info_hash = self.ImageHash

oImages = self.oImages

oFiles = self.oFiles

max_width = self.max_width

;only draw the image if we have not yet

if ~info_hash.haskey(strlowcase(drawfile)) then begin

;save the drawfile

oFiles.add,drawfile

;new hash to add for file

file_hash = orderedhash()

;only check if we have drawn something to the screen

if (n_elements(oDrawnAlready) gt 0) then begin

idx = (where(drawfile eq oDrawnAlready.ToArray()))[0]

if (idx ne -1) then begin

print,'Image drawn already, returning...'

return

endif

endif

;read the image data

dat = read_image(drawfile)

dims = size(dat, /DIMENSIONS)

;remember the iamge diensions

file_hash['DIMS'] = dims

;assume the number of channels is the smallest dimension

;if we have more than one, then we need to account for interleave change to BSQ

if (n_elements(dims) eq 2) then begin

nchannels = 1

;calculate aspect ratio

aspect = float(dims[1])/dims[0]

;resize data so we use less memory

dat = congrid(dat, dims[0]/4, dims[1]/4, /INTERP)

endif else begin

nchannels = min(dims)

channel = (where(dims eq nchannels))[0]

;change image interleave to BSQ

case channel of

0:dat = transpose(dat, [1,2,0])

1:dat = transpose(dat, [0,2,1])

2:;do nothing

endcase

dims = size(dat, /DIMENSIONS)

;calculate aspect ratio

aspect = float(dims[1])/dims[0]

;resize data to consume less memory

dat = congrid(dat, dims[0]/4, dims[1]/4, nchannels, /INTERP)

endelse

;add a random offset to the image so they don't all line up

;on top of one another

center = [.5, .5] + randomu(!NULL)/10

;determine the image position

if (aspect gt 1) then begin

xrange = center[0] + (max_width/aspect)*[-.5, .5]

yrange = center[1] + max_width*[-.5, .5]

endif else begin

xrange = center[0] + max_width*[-.5, .5]

yrange = center[1] + (aspect*max_width)*[-.5, .5]

endelse

img_pos = [xrange[0], yrange[0], xrange[1], yrange[1]]

img = image(dat, CURRENT = current, POSITION = img_pos)

oImages.add, img

;get the original scale factors, scale centers, and positions

file_hash['SCALE_CENTER'] = img.SCALE_CENTER

file_hash['SCALE_FACTOR'] = img.SCALE_FACTOR

file_hash['POSITION'] = img.POSITION

file_hash['XRANGE'] = img.XRANGE

file_hash['YRANGE'] = img.YRANGE

;default rotation to 0

file_hash['ROTATION'] = 0

;save the image information

info_hash[strlowcase(drawfile)] = file_hash

endif else begin

print, 'Image drawn already, skipping...'

endelse

end

pro ImageInfo::DeselectAllImages

compile_opt idl2

;disable window update

self.oWin.Refresh, /disable

foreach img, self.oImages do img.Select, /CLEAR

;refresh the window

self.oWin.Refresh

end

pro Imageinfo::ResetImages

compile_opt idl2

;get the reference to the images that are selected

oImages = self.oImages

;disable window update

self.oWin.Refresh, /disable

selected = self.oWin.GetSelect()

keys = self.ImageHash.keys()

foreach img, selected do begin

idx = (where(img eq self.oImages.toarray()))[0]

;reset rotation

img.rotate, /reset

;return properties to their initial values

img.SCALE_CENTER = (self.ImageHash[keys[idx]])['SCALE_CENTER']

img.SCALE_FACTOR = (self.ImageHash[keys[idx]])['SCALE_FACTOR']

img.XRANGE = (self.ImageHash[keys[idx]])['XRANGE']

img.YRANGE = (self.ImageHash[keys[idx]])['YRANGE']

img.POSITION = (self.ImageHash[keys[idx]])['POSITION']

img.POSITION = (self.ImageHash[keys[idx]])['POSITION']

img.Order, /BRING_TO_FRONT

endforeach

;refresh the window

self.oWin.Refresh

end

pro ImageInfo::BringImagesForward

compile_opt idl2

foreach selected, self.oWin.GetSelect() do selected.Order, /BRING_FORWARD

end

pro ImageInfo::RemoveImages

compile_opt idl2

;get the selected images

selected = self.oWin.GetSelect()

;disable window update

self.oWin.Refresh, /disable

foreach img, selected do begin

;get the reference to the image list

oImages = self.oImages

oFiles = self.oFiles

;get the leftover hash keys

keys = self.ImageHash.keys()

;figure out which image was clicked on

idx = (where(img eq oImages.toarray()))[0]

imageref = oImages[idx]

;delete graphics

imageref.Delete

;remove reference from the list

oImages.Remove, idx

oFiles.Remove, idx

;remove the key from the hash

self.ImageHash.Remove, keys[idx]

endforeach

;refresh the display

self.oWin.Refresh

end

pro ImageInfo::ResetImageRotations

compile_opt idl2

;get the reference to the images that are selected

oImages = self.oImages

imagehash = self.imagehash

keys = imagehash.keys()

;disable window update

self.oWin.Refresh, /disable

selected = self.oWin.GetSelect()

foreach img, selected do begin

;find the index for which image is selected

img_idx = (where(oImages.toarray() eq img))[0]

;reset rotation in the iamge hash

(imagehash[keys[img_idx]])['ROTATION'] = 0

;reset rotation

img.rotate, /reset

endforeach

;refresh the window

self.oWin.Refresh

end

pro Imageinfo::ResetImageZooms

compile_opt idl2

;get the reference to the images that are selected

oImages = self.oImages

;disable window update

self.oWin.Refresh, /disable

selected = self.oWin.GetSelect()

keys = self.ImageHash.keys()

foreach img, selected do begin

idx = (where(img eq self.oImages.toarray()))[0]

;return properties to their initial values

img.SCALE_CENTER = (self.ImageHash[keys[idx]])['SCALE_CENTER']

img.SCALE_FACTOR = (self.ImageHash[keys[idx]])['SCALE_FACTOR']

img.XRANGE = (self.ImageHash[keys[idx]])['XRANGE']

img.YRANGE = (self.ImageHash[keys[idx]])['YRANGE']

endforeach

;refresh the window

self.oWin.Refresh

end

pro ImageInfo::RotateImages, theta

compile_opt idl2

;get the reference to the images that are selected

oImages = self.oImages

;disable window update

self.oWin.Refresh, /disable

selected = self.oWin.GetSelect()

keys = self.ImageHash.keys()

foreach img, selected do begin

;figure out which image was clicked on

idx = (where(img eq self.oImages.toarray()))[0]

imagehash = self.ImageHash[keys[idx]]

imageref = oImages[idx]

;reset the image, rotate by theta

imageref.Rotate, /RESET

imagehash['ROTATION']+=theta

imageref.Rotate, imagehash['ROTATION']

endforeach

self.oWin.Refresh

end

pro ImageInfo::SelectAllImages

compile_opt idl2

;disable window update

self.oWin.Refresh, /disable

foreach img, self.oImages do img.Select, /ADD

;refresh the window

self.oWin.Refresh

end

pro ImageInfo::SendImagesBack

compile_opt idl2

foreach selected, self.oWin.GetSelect() do selected.Order, /SEND_BACKWARD

end

;method to convert iamges to ENVI formatted files, OUTFILES is not an input keyword, but output

pro ImageInfo::StitchImages, DOWNSIZE = downsize, OUTFILES = outfiles

compile_opt idl2

;select all of the images that are displayed

images = self.oImages.toarray()

files = self.oFiles.toarray()

keys = self.ImageHash.keys()

;make sure we have some images to process

if (n_elements(files) gt 0) then begin

outfiles = strarr(n_elements(images))

;start ENVI

e = envi(/current)

if (e eq !NULL) then begin

e = envi(/headless)

nokill = 1

endif

; create ENVI formatted files

print, 'Converting images to ENVI formatted files...'

for i=0,n_elements(images)-1 do begin

img = images[i]

pos = img.position

top_left = [pos[0], pos[3]] - .5d

imagehash = self.imagehash[keys[i]]

dims = imagehash['DIMS']

dims_2d = dims[where(dims ne 3)]

pix_size = [pos[2] - pos[0],pos[3] - pos[1]]/(dims_2d/downsize)

outfile = strmid(files[i], 0, strpos(files[i],'.', /REVERSE_SEARCH)) + '_envi.dat'

;check if file exists and delete it

if file_test(outfile) then FILE_DELETE, outfile, /QUIET

;if it still exists after deleting, then another program has a lock on the file

if file_test(outfile) then begin

print, 'File "' + outfile + '" locked by another program, skipping...'

continue

endif

outfiles[i] = outfile

dat = read_image(files[i])

dims = size(dat, /DIMENSIONS)

nchannels=1

;change interleave to BSQ

if (n_elements(dims) gt 2) then begin

nchannels = min(dims)

channel = (where(dims eq nchannels))[0]

;change image interleave to BSQ

case channel of

0:dat = transpose(dat, [1,2,0])

1:dat = transpose(dat, [0,2,1])

2:;do nothing

endcase

dims = size(dat, /DIMENSIONS)

endif

;resize the image if needed

if (downsize gt 1) then begin

if (nchannels eq 1) then begin

dat = congrid(dat, floor(dims[0]/downsize), floor(dims[1]/downsize), /INTERP)

endif else begin

dat = congrid(dat, floor(dims[0]/downsize), floor(dims[1]/downsize), nchannels, /INTERP)

endelse

endif

;rotate the image if the user rotated the image with the degree buttons

if (imagehash['ROTATION'] ne 0) then begin

print, string(9b) + 'Rotating image, may take a minute or two depending on size...'

dat = RotateImage(dat, imagehash['ROTATION'])

endif

;assume the number of channels is the smallest dimension

;if we have more than one, then we need to account for interleave change to BSQ

if (n_elements(dims) eq 2) then begin

nchannels = 1

;rotate image to have ENVI orientation

dat = rotate(dat, 7)

endif else begin

nchannels = min(dims)

channel = (where(dims eq nchannels))[0]

;change image interleave to BSQ

case channel of

0:dat = transpose(dat, [1,2,0])

1:dat = transpose(dat, [0,2,1])

2:;do nothing

endcase

for z=0,nchannels-1 do begin

dat[*,*,z] = rotate(reform(dat[*,*,z]),7)

endfor

endelse

;create a spatial reference

spatialref = ENVIStandardRasterSpatialRef($

coord_sys_code = 4326, $

/GEOGCS,$

pixel_size = pix_size, $

tie_point_pixel = [0, 0], $

tie_point_map = top_left)

newraster = ENVIRaster(dat, URI = outfile, SPATIALREF = spatialref)

;add a data ignore value if we had rotation

if (imagehash['ROTATION'] ne 0) then begin

newraster.METADATA.AddItem, 'data ignore value', 0

endif

;save and close the raster

newraster.save

newraster.close

print, string(9b) + 'Converted ' + strtrim(i+1,2) + ' of ' +  strtrim(n_elements(images),2) + ' images, outfile:'

print, string(9b) + string(9b) + outfile

endfor

;close ENVI if we started it during this method

if (nokill eq !NULL) then e.close

endif else begin

print, 'No images selected, returning'

return

endelse

end

pro ImageInfo__define

compile_opt idl2

struct = {$

IMAGEINFO,$

ImageHash:orderedhash(),$

oImages:list(),$

oFiles:list(),$

oWin:obj_new(),$

max_width:1d}

end

function RotateImage, img_dat, theta

compile_opt idl2, hidden

;get the data dimensions

dims = size(img_dat, /DIMENSIONS)

nchannels = 1

if ~keyword_set(orientation) then orientation = 2

;apply rotation for nchannel images

ndims = n_elements(dims)

if (ndims gt 2) then begin

nchannels = min(dims)

channel = (where(dims eq nchannels))[0]

;change image interleave to BSQ

case channel of

0:img_dat = transpose(img_dat, [1,2,0])

1:img_dat = transpose(img_dat, [0,2,1])

2:;do nothing

endcase

dims = size(img_dat, /DIMENSIONS)

endif

;calculate our rotation matrix

theta*=!DTOR

r_mat = [[ cos(theta), sin(theta), 0],$

[-sin(theta), cos(theta), 0],$

[     0     ,      0    , 0]]

;pre-allocate an array for the initial pixel locations

xy0 = make_array(3, dims[0]*dims[1], TYPE = 12, /NOZERO)

;fill xy0 with values

indices = lindgen(dims[0]*dims[1])

xy0[0,*] = indices mod dims[0]

xy0[1,*] = indices/dims[0]

xy0[2,*] = replicate(1.0,1,dims[0]*dims[1])

delvar, indices

;rotate our initial positions to find out the new pixel locations

xy1 = matrix_multiply(r_mat, xy0)

;grab the individual xy new locations

xind = xy1[0,*]

yind = xy1[1,*]

;create output grid for out image

xout = floor(xind.min()) + dindgen(ceil(xind.max() - xind.min()))

yout = floor(yind.min()) + dindgen(ceil(yind.max() - yind.min()))

;use custom, amazing interpolation

new_dims = [n_elements(xout), n_elements(yout)]

out_dat = make_array(new_dims[0], new_dims[1], nchannels,TYPE = img_dat.typecode, VALUE = 0)

for i=0, nchannels-1 do begin

channel_dat = reform(out_dat[*,*,i])

channel_dat[xind - xind.min(), yind-yind.min()] = img_dat[*,*,i]

;fill in missing holes

zero = where(channel_dat eq 0, zero_count)

if (zero_count gt 0) then begin

for z=0, zero_count-1 do begin

;check if we have neighbors that are not equal to 0

xidx = zero mod new_dims[0]

yidx = zero / new_dims[0]

for z=0, zero_count-1 do begin

sub = channel_dat[xidx[z]-1 > 0:xidx[z]+1 < (new_dims[0]-1), yidx[z]-1 > 0:yidx[z]+1 < (new_dims[1]-1)]

close_nozero = where(sub ne 0, nozero_count)

if (nozero_count gt 6) then channel_dat[xidx[z],yidx[z]] = total(sub[close_nozero])/nozero_count

endfor

endfor

endif

out_dat[*,*,i] = channel_dat

endfor

;remove any irrelevant indices

return, reform(out_dat)

end

pro IDLMosaic_event, event

;help, event

widget_control, event.top, get_uvalue=info

;get the image properties lists

oImageInfo = info.oImageInfo

;turn on the hourglass

widget_control, HOURGLASS=1

;check if we have a kill request event

case TAG_NAMES(event, /STRUCTURE_NAME) of

'WIDGET_KILL_REQUEST':BEGIN

!except = info.orig_except

WIDGET_CONTROL, event.top, /DESTROY

retall

END

ELSE: ;do nothing

endcase

;handle the event by the widget that was pressed

case event.id of

;user double clicked on a list element for all of the images

;so we will draw it

info.wList:begin

if (event.CLICKS eq 2) then begin

;get the list of files

files = info.files

;draw the image

oImageInfo.AddImage, files[event.index], CURRENT = info.oWin

endif

end

;user wants to zoom out

info.wZoomOut:begin

info.oWin.ZoomOut

info.oWin.Refresh

end

;user wants to zoom in

info.wZoomIn:begin

info.oWin.ZoomIn

info.oWin.Refresh

end

;bring selected items forward

info.wBringForward:begin

info.oImageInfo.BringImagesForward

end

;send selected items forward

info.wSendBack:begin

info.oImageInfo.SendImagesBack

end

;reset the image to have all of its original properties

info.wResetImage:begin

info.oImageInfo.ResetImages

end

;reset the image zoom only

info.wResetZoom:begin

info.oImageInfo.ResetImageZooms

end

;reset the image rotation

info.wResetRotation:begin

info.oImageInfo.ResetImageRotations

end

;deselect all images

info.wDeselectAll:begin

info.oImageInfo.DeselectAllImages

end

;select all images

info.wSelectAll:begin

info.oImageInfo.SelectAllImages

end

;remove selected images

info.wDeleteImage:begin

info.oImageInfo.RemoveImages

end

;images are rotated

info.wRotCSmall:begin

info.oImageInfo.RotateImages, 0.2

end

info.wRotCCSmall:begin

info.oImageInfo.RotateImages, -0.2

end

info.wRotCMedium:begin

info.oImageInfo.RotateImages, 1.0

end

info.wRotCCMedium:begin

info.oImageInfo.RotateImages, -1.0

end

info.wRotCLarge:begin

info.oImageInfo.RotateImages, 5.0

end

info.wRotCCLarge:begin

info.oImageInfo.RotateImages, -5.0

end

;convert images to ENVI formatted files

info.wStitchImages:begin

info.oImageInfo.StitchImages, DOWNSIZE = info.downsize, OUTFILES = outfiles

;check if the ENVI workbench is open, if so then ask if the user wants to open up the images

;if ENVI is not open, then open it now

msg = dialog_message('Would you like to open the ENVI workbench and display the images in ENVI?', /QUESTION)

if (msg eq 'Yes') then begin

e = envi(/current)

if (e eq !NULL) then begin

e = envi()

endif else begin

;open up the ENVI Workbench if not open already

if (e.widget_id eq 0) then begin

e.close

e = envi()

endif

endelse

;disable ENVI Workbench update

e.refresh, /disable

;add each image to ENVI's view

view = e.GetView()

foreach file, outfiles do begin

if (file eq '') then continue

raster = e.openraster(file)

layer = view.createlayer(raster)

endforeach

;refresh ENVI display

e.refresh

endif

;destroy the widget

WIDGET_CONTROL, event.top, /DESTROY

end

;user resized the base

info.wBase:begin

widget_control, info.wDraw, xSize=event.y, ySize=event.y

end

else:;print, 'dunno what to do with id ', event.id

endcase

widget_control, HOURGLASS=0

end

;+

;

;

; :Keywords:

;    DOWNSIZE: in, optional, type=int, default=4

;       Set this keyword to an integer which represents the resize factor when the

;       iamges are created as ENVI formatted files. For example, setting this keyword to 3

;       will regrid the results to have 1/3 the X and Y dimensions that they originally have.

;    EXTENSION: in, optional, type=string, default='jpg'

;       This keyword represents the file extensions that will be searched for if you set the

;       PICKDIR keyword.

;    MAX_WIDTH: in, optional, type=float, default=.33, min=.1, max=.5

;       Setting this keyword will control the maximum size that an image can be when added to

;       the widget. Setting this value to a smaller number will allow for more images to be added

;       to the display.

;    PICKDIR: in, optional, type=sting

;       This optional keyword can be set to a directory that contains images you want to use

;       with IDLMosaic. The directory that this keyword is set to will be searched for files

;       ending with EXTENSION. Make sure you change

;

; :Author: Norm3596

;-

pro IDLMosaic, DOWNSIZE = downsize, EXTENSION = extension, MAX_WIDTH = max_width, PICKDIR = pickdir

compile_opt idl2

ireset, /no_prompt

;select a directory of files

if keyword_set(pickdir) then begin

if (extension eq !NULL) then extension = 'jpg'

if ~file_test(pickdir) then message, 'PICKDIR specified, but directory does not exist!'

cd, pickdir, CURRENT = first_dir

files = file_search('*' + extension, COUNT=nfiles)

cd, first_dir

if (nfiles eq 0) then message, 'No files found with extension!'

;sort the found files and prepend the input directory

files = (pickdir + path_sep() + files).sort()

;default is to use dialog pickfile to select images

endif else begin

;use dialog pickfile to select images

files = dialog_pickfile(/MULTIPLE_FILES)

nfiles = n_elements(files)

if (files[0] eq '') then message, 'No files chosen!'

endelse

;check if we want to downsample the rasters

;by default this is true because the size in a .dat file of a JPEG is much

;greater than just the jpeg image itself

if keyword_set(downsize) then downsize = downsize else downsize = 4

;keyword for setting the maximum width of the iamges in the display window

if ~keyword_set(max_width) then max_width = .33

;make suze max_width falls within realistic values to fit in our window

max_width>=.1

max_width<=.5

;create starting widgets

wBase = widget_base(/row, title='IDLMosaic', tlb_size_events=0)

;create the draw widget

window_size = [800, 800]

wDraw = widget_window(wBase, xsize = window_size[0]+1, ysize = window_size[1]+1, $

X_SCROLL_SIZE = window_size[0], Y_SCROLL_SIZE = window_size[1])

wButtonCol = widget_base(wBase, /col)

wStatus = widget_label(wButtonCol,  /dynamic_resize, value='Images:')

;create a list widget

wList = widget_list(wButtonCol, VALUE = file_basename(files), YSIZE = nfiles<10)

;crete buttons for zooming in and out

wZoomIn = widget_button(wButtonCol, /dynamic_resize, value='Zoom In')

wZoomOut = widget_button(wButtonCol, /dynamic_resize, value='Zoom Out')

;reset the view for the image

wImageText = widget_label(wButtonCol, value='Image Actions:')

wResetImage = widget_button(wButtonCol, /dynamic_resize, value='Reset Properties')

wResetZoom = widget_button(wButtonCol, /dynamic_resize, value='Reset Zoom')

wResetRotation = widget_button(wButtonCol, /dynamic_resize, value='Reset Rotation')

;crete buttons for zooming in and out

wBringForward = widget_button(wButtonCol, /dynamic_resize, value='Bring Forward')

wSendBack = widget_button(wButtonCol, /dynamic_resize, value='Send Back')

;add buttons for rotating the images

wRotateClockwise = widget_label(wButtonCol, value='Rotate Clockwise:')

wBaseClockwise = widget_base(wButtonCol, /ROW, /ALIGN_CENTER)

wRotCSmall = widget_button(wBaseClockwise, /dynamic_resize, value='0.2')

wRotCMedium = widget_button(wBaseClockwise, /dynamic_resize, value='1.0')

wRotCLarge = widget_button(wBaseClockwise, /dynamic_resize, value='5.0')

wRotateCounterClockwise= widget_label(wButtonCol, value='Rotate Counter-clockwise:')

wBaseCClockwise = widget_base(wButtonCol, /ROW, /ALIGN_CENTER)

wRotCCSmall = widget_button(wBaseCClockwise, /dynamic_resize, value='0.2')

wRotCCMedium = widget_button(wBaseCClockwise, /dynamic_resize, value='1.0')

wRotCCLarge = widget_button(wBaseCClockwise, /dynamic_resize, value='5.0')

;crete buttons for zooming in and out

wSelectAll = widget_button(wButtonCol, /dynamic_resize, value='Select All')

wDeselectAll = widget_button(wButtonCol, /dynamic_resize, value='Deselect All')

wDeleteImage = widget_button(wButtonCol, /DYNAMIC_RESIZE, VALUE='Remove Images')

wStitchImages = widget_button(wButtonCol, /DYNAMIC_RESIZE, VALUE='Done!')

widget_control, wBase, /realize

; Retrieve the newly-created Window object.

widget_control, wDraw, get_value=oWin

;remember the original valu of !EXCEPT

orig_except = !EXCEPT

!EXCEPT=0

info = {$

wBase:wBase,$

wDraw:wDraw,$

wStatus:wStatus,$

wList:wList,$

wZoomOut:wZoomOut,$

wZoomIn:wZoomIn,$

wBringForward:wBringForward,$

wSendBack:wSendBack,$

wResetImage:wResetImage,$

wResetZoom:wResetZoom,$

wResetRotation:wResetRotation,$

wSelectAll:wSelectAll,$

wDeselectAll:wDeselectAll,$

wDeleteImage:wDeleteImage,$

wStitchImages:wStitchImages,$

wRotCSmall:wRotCSmall,$

wRotCCSmall:wRotCCSmall,$

wRotCMedium:wRotCMedium,$

wRotCCMedium:wRotCCMedium,$

wRotCLarge:wRotCLarge,$

wRotCCLarge:wRotCCLarge,$

oWin:oWin,$

oImageInfo:ImageInfo(oWin),$

except_orig:orig_except,$

files:files,$

downsize:downsize,$

max_width:max_width}

;set the uvalue

widget_control, wBase, set_uvalue=info

;register the widget with our event handler

;keep this widget as blocking

xmanager, 'IDLMosaic', wBase, /NO_BLOCK

end

Managing a Graphical User Interface within an Object Using List and Hash Parameters with GSF 2.0