跳转至

用 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

在对象中管理图形用户界面 使用 GSF 2.0 的列表和哈希参数