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.
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