用 ENVI + IDL 发挥创意
原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/getting-creative-with-envi-idl
7584 给本文评分:
5.0
用 ENVI + IDL 发挥创意
Zachary Norman 2016年5月6日,星期五
说到 ENVI + IDL,我喜欢发挥创意,看看利用我手头上所有这些强大的工具,我还能实现些什么。为了让您了解这篇博客的背景,几个月前我拿到了我的第一台单反相机。当我想拍摄更大场景的照片时,我意识到我没有生成广阔景观的镶嵌图或全景图所需的工具。
根据之前的一篇博客文章 从冥王星图像创建镶嵌图,我使用了一种非常简单的方法来关联图像之间的相对位置,那就是在每张图像之间设置一个恒定的偏移量。如果是手持拍照,这样做就有点困难了,所以我求助于我的好朋友 IDL 来帮忙。
利用 IDL 的图形函数,您可以创建交互式图形窗口。为了正确摆放我的图像,我决定使用这个方法,因为我可以交互式地移动不同的图像,并把它们放到该放的位置上。然后,根据它们在窗口中的最终位置,我可以为 ENVI 创建空间参考,以了解图像彼此之间的相对位置。这一点很重要,因为如果您想使用 ENVI 的无缝镶嵌工具或图像配准工作流,就需要为您的图像提供空间参考。
以下是我用一些示例图像创建的小部件的界面截图:
要使用该工具,您需要下载代码(见下文)。获取代码并运行程序后,会出现一个对话框,要求您选择要加载的一些图像。允许的格式是任何能被 IDL 的 READ_IMAGE 函数读取的图像。选择图像后,将出现上述的小部件界面,您可以对选中的图像(蓝色框框住的图像)执行不同的操作。要将图像加载到显示中,只需在图像右上角的文件名上双击即可。如果任何图像的位置、方向或缩放变得奇怪,只需选中该图像并按"重置属性"按钮。
完成后,按"完成!"按钮。这将为显示中的每个图像创建 ENVI 格式的文件。完成后,会出现提示询问您是否要在 ENVI 中打开这些图像。如果选择"是",那么在图像加载后您会看到类似以下的内容:
此时,在使用无缝镶嵌工具之前,您可能需要使用图像配准工作流来更好地对齐图像。祝您镶嵌愉快!
以下是我称之为 IDLMosaic 的小部件代码。您可以随意以任何方式编辑/修改代码。将代码复制粘贴到 IDL 中并另存为 "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