;+
; NAME:
;       GETIMAGE
;
; PURPOSE:
;
;       The purpose of this function is to allow the user to open either
;       unformmated or XDR binary image files of up to eight dimensions.
;
; CATEGORY:
;
;       Widgets, File I/O.
;
; CALLING SEQUENCE:
;
;       image = GETIMAGE(filename)
;
; OPTIONAL INPUTS:
;
;       filename: The name of the file to open for reading.
;
; OPTIONAL KEYWORD PARAMETERS:
;
;       CANCEL: An output variable that can be set to a named variable.
;       The value of the return variable will be 1 if the user clicked
;       the "Cancel" button or if there was a problem reading the file.
;
;       DATATYPE: The "type" of data you wish to read out of the file.
;       The data type corresponds to the Size(image, /TYPE) value. Here
;       are the types supported:
;
;       BYTE                   1 (default)
;       INTEGER                2
;       LONG INTEGER           3
;       FLOAT                  4
;       DOUBLE                 5
;       UNSIGNED INTEGER      12
;       UNSIGNED LONG INTEGER 13
;       64-bit LONG           14
;       64-bit UNSIGNED LONG  15
;
;
;
;       DIMENSIONS: A vector of image dimensions. The default value is [256, 256].
;
;       DIRECTORY: The name of the directory the file is located in. By
;       default the program looks in the "coyote" directory under the
;       main IDL directory, if one exists. Otherwise, it defaults to the
;       current directory.
;
;       ENDIAN: Set this keyword to an integer that indicates the byte
;       ordering of the data file. If you don't know what byte order means,
;       or you don't know anything about the byte order of the data, or
;       if you are sure the data was created on the same type of machine
;       you are now running IDL on, then just accept the default of 0 or
;       "native" ordering. If you are wrong, you will soon know it and you
;       can set the keyword to another value on your next try. :-)
;
;       If you know the machine was created on a big endian machine (such
;       as a Sun or HP workstation), set this value to 1 (Big Endian). If e
;       you are sur the image data was create on a little endian machine (such
;       as a Windows PC or laptop running LINUX), set the value to 2 (Little Endian).
;
;       HEADER: The size of any header information in the file in BYTES.
;       Default is 0.
;
;       HEADDATA: An optional output keyword that will contain the header
;       information read from the file.
;
;       PARENT: The group leader for this widget program. The PARENT is
;       required if GETIMAGE is called from another widget program in order
;       to make this program a MODAL widget program.
;
;       XDR: Set this keyword if the binary file is of XDR type. The default
;       type is "Unformatted".
;
;       XOFFSET: This is the X offset of the program on the display. The
;       program will be placed approximately in the middle of the display
;       by default.
;
;       YOFFSET: This is the Y offset of the program on the display. The
;       program will be placed approximately in the middle of the display
;       by default.
;
; COMMON BLOCKS:
;
;       None.
;
; SIDE EFFECTS:
;
;       A "CANCEL" operation is indicated by a 0 return value.
;       Any error in reading the file results in a 0 return value.
;
; EXAMPLE:
;       To load the image "galaxy.dat" in the $IDL/examples/data
;       directory, type:
;
;       image = GETIMAGE('galaxy.dat', DIRECTORY=!DIR + '/examples/data', $
;          DIMENSIONS=[256,256], Cancel=cancelled, Parent=event.top)
;       IF NOT cancelled THEN TV, image
;
; MODIFICATION HISTORY:
;       Written by: David Fanning, 3 February 96.
;       Fixed bug that prevented reading INTEGER data. 19 Dec 96.
;       Modifed program for IDL 5 MODAL operation. 19 Oct 97.
;       Added CANCEL keyword. 27 Oct 97. DWF.
;       Fixed CANCLE keyword spelling. Sigh... 29 JUN 98. DWF.
;       Added COYOTE_FIELD, improved appearance. 19 NOV 99. DWF.
;       Updated with latest version of COYOTE_FIELD. 18 FEB 2000. DWF.
;       Added CATCH keyword so the program will break when I want
;       it to. :-) 18 MAR 2000. DWF.
;       Added GROUP_LEADER keyword, which is synonymous with PARENT. 31 MAR 2000. DWF.
;       Updated obsolete PICKFILE call to DIALOG_PICKFILE. 17 JAN 2001. DWF.
;       Extensive update for IDL Programming Techniques, 3rd Edition. 1 November 2006. DWF.
;          XSIZE, YSIZE, CATCH, and FRAMES keyword made obsolete.
;          HEADDATA, ENDIAN, DATATYPE, DIMENSIONS keywords added.
;       Added ability to parse fully qualified file names passed from Dialog_Pickfile. 30 Oct 2010. DWF.
;       IF a file name is not passed into the program, it asks the user to select one now. 10 Jan 2011. DWF.
;       Problem with SWAP_ENDIAN keywords fixed. 7 March 2011. DWF.
;-
;
;******************************************************************************************;
;  Copyright (c) 2008, by Fanning Software Consulting, Inc.                                ;
;  All rights reserved.                                                                    ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;
PRO GETIMAGE_NULL_EVENTS, event

   ; The purpose of this event handler is to do nothing
   ; and ignore all events that come to it.

END ;-------------------------------------------------------------------------



PRO GETIMAGE_DIMS_EVENTS, event

   ; The purpose of this event handler is to make the proper dimension widgets
   ; sensitive or insensitive.
   Widget_Control, event.top, Get_UValue=info
   num = event.index + 1
   FOR j=0,num-1 DO Widget_Control, info.d[j], Sensitive=1
   FOR j=num, 7 DO Widget_Control, info.d[j], Sensitive=0, Set_Value=0

END ;-------------------------------------------------------------------------



PRO GETIMAGE_XDR_EVENTS, event

   ; The purpose of this event handler is to make the byte order widget
   ; sensitive or insensitive depending on file format.
   Widget_Control, event.top, Get_UValue=info

   IF event.index THEN Widget_Control, info.byteorderID, Sensitive=0 ELSE $
      Widget_Control, info.byteorderID, Sensitive=1

END ;-------------------------------------------------------------------------



FUNCTION GETIMAGE_FIND_COYOTE

   ; The purpose of this function is to find the "coyote"
   ; training directory and return its path. If no
   ; directory is found, the function returns a null string.
   ON_ERROR, 1

   ; Check this directory first.
   CD, Current=thisDir
   IF STRPOS(STRUPCASE(thisDir), 'COYOTE') GT 0 THEN RETURN, thisDir

   ; Look in !Path directories.
   pathDir = EXPAND_PATH(!Path, /Array)
   s = SIZE(pathDir)
   IF s[1] LT 1 THEN RETURN, ''
   FOR j=0,s[1]-1 DO BEGIN
      check = STRPOS(STRUPCASE(pathDir[j]), 'COYOTE')
      IF check GT 0 THEN RETURN, pathDir[j]
   ENDFOR
   RETURN, ''

END ;-------------------------------------------------------------------------



PRO GETIMAGE_EVENT, event

   ; The only events that can come here are button events.

   ; Get the info structure out of the user value of the top-level base.
   Widget_Control, event.top, Get_UValue=info

   ; There may be errors we can't anticipate. Catch them here, alert the
   ; user as to what the error was, and exit the event handler without
   ; doing any damage.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      ok = cgErrorMsg()
      formdata = {cancel:1}
      *info.ptrToFormData =formdata
      RETURN
   ENDIF

   ; Which button caused this event?
   Widget_Control, event.id, Get_Value=buttonValue

   CASE buttonValue OF

      'Pick Filename': BEGIN

         ; Start in the directory listed in the directory text widget.
         ; Convert the text value to a scalar.
         Widget_Control, info.dirnameID, Get_Value=startDirectory
         startDirectory = startDirectory[0]

         ; If this directory doesn't exist, use the current directory.
         test = File_Search(startDirectory, Count=foundfile)
         IF foundfile NE 1 THEN CD, Current=startDirectory

         ; Use PICKFILE to pick a name.
         pick = Dialog_Pickfile(Path=startDirectory, /NoConfirm, $
            Get_Path=path, Filter='*.*')

         ; Set the directory text widget with the name of the directory.
         ; Make sure the user didn't cancel out of PICKFILE.
         IF pick NE '' THEN BEGIN

            ; Find the lengths of the PICK and the PATH.
            pathLen = StrLen(path)
            picklen = StrLen(pick)

           ; Shorten the PATH to take off last file separator.
            path = StrMid(path, 0, pathLen-1)

            ; Put the PATH in the directory location.
            Widget_Control, info.dirnameID, Set_Value=path

            ; Set the filename text widget with the name of the file.
            filename = StrMid(pick, pathlen, picklen-pathlen)
            Widget_Control, info.filenameID, Set_Value=filename

         ENDIF

         END ; of the Pick Filename button case

       'Cancel': BEGIN

         ; Have to exit here gracefully. Set the "CANCEL" flag.
         formdata = {cancel:1}
         *info.ptrToFormData =formdata

         ; Out of here!
         Widget_Control, event.top, /Destroy
         END ; of the Cancel button case

       'Accept': BEGIN  ; Gather the form information.

          ; Put the directory and filename together to make a path.
          Widget_Control, info.dirnameID, Get_Value=directory
          Widget_Control, info.filenameID, Get_Value=file

          filename = Filepath(Root_Dir=directory[0],file[0])

          ; Get the size and header info. Remember these are Pointers to Integers!
          Widget_Control, info.headerID, Get_Value=header
          header = Long(header[0])

          ; Get the number of dimensions.
          num = Widget_Info(info.dimsID, /Droplist_Select) + 1

          ; Get a vector of dimensions. If any of these numbers is 0, you
          ; have a problem.
          dimensions = IntArr(num)
          FOR j=0,num-1 DO BEGIN
            Widget_Control, info.d[j], Get_Value=theDimension
            IF theDimension EQ 0 THEN Message, 'Dimension ' + StrTrim(j+1,2) + ' cannot be zero.'
            dimensions[j] = theDimension
          ENDFOR

          ; Get the data type from the droplist widget.
          listIndex = Widget_Info(info.datatypesID, /Droplist_Select)
          type = info.datatypes(listIndex)

          ; What kind of endness do we have?
          endian = Widget_Info(info.byteorderID, /Droplist_Select)

          ; Get the format index from the formatlist widget.
          xdr = Widget_Info(info.formatlistID, /Droplist_Select)

          ; If we want XDR, then the endian order is always "native".
          IF xdr THEN endian = 0

          ; Create the formdata structure from the information you collected.
          formdata = {header:header, dimensions:dimensions, endian:endian, $
             filename:filename, type:type, xdr:xdr, cancel:0}

          ; Store the formdata in the pointer location.
          *info.ptrToFormData = formdata

         ; Out of here!
         Widget_Control, event.top, /Destroy
         END ; of the Accept button case

   ENDCASE

END ; of GETIMAGE_EVENT event handler ***************************************



FUNCTION GETIMAGE, filename, Directory=directory, DataType=datatype, Dimensions=dimensions, $
   Header=header, HeadData=headdata, Parent=parent, XDR=xdr, XOffSet=xoffset, $
   YOffSet=yoffset, Cancel=canceled, Group_Leader=group_leader, Endian=endian, $
   XSIZE=xsize, YSIZE=ysize, FRAMES=frames, Catch=catchit  ; Obsolete keywords on this line

   ; This is a function to specify the size, data type, and header information
   ; about an image that you would like to read. It reads the data and returns
   ; it as the result of the function. If an error occurs or the user CANCELS,
   ; the function returns a 0.
   On_Error, 2

   ; Catch errors unless explicitly told not to.
   IF N_Elements(catchit) EQ 0 THEN catchit = 1  ; Now obsolete. Doing nothing with value.

   ; Respond to either PARENT or GROUP_LEADER keywords.
   IF N_Elements(parent) NE 0 THEN group_leader = parent

   ; Check for parameters and keywords.
   IF N_Elements(filename) EQ 0 THEN BEGIN
        filename=Dialog_Pickfile()
        IF filename EQ "" THEN RETURN, ""
   ENDIF
   
   ; Does the file name have a directory? Is so, use it.
   fileDir = File_Dirname(filename)
   IF StrLen(fileDir) GT 1 THEN BEGIN
       filename = File_Basename(filename)
       directory = fileDir
   ENDIF ELSE BEGIN
       IF fileDir EQ "." THEN BEGIN
          CD, CURRENT=directory
          filename = File_Basename(filename)
       ENDIF
   ENDELSE

   ; If DIRECTORY keyword is not used, use the IDL example/data directory.
   IF N_Elements(directory) EQ 0 THEN BEGIN
       directory = File_DirName(Filepath(Subdirectory=['examples', 'data'], 'nonesense.dat'))
   ENDIF 
   startDirectory = directory
   
   ; Check for size and header keywords. These probably come in as
   ; numbers and you need strings to put them into text widgets.
   IF N_Elements(endian) EQ 0 THEN endian = 0 ELSE endian = 0 > endian < 2
   IF N_Elements(datatype) EQ 0 THEN datatype = 1
   IF N_Elements(dimensions) EQ 0 THEN dimensions = [256, 256]
   IF N_Elements(header) EQ 0 THEN header='0' ELSE header=StrTrim(header,2)

   ; Create a modal top-level base if group leader is present.
   IF N_Elements(group_leader) EQ 0 THEN $
      tlb = Widget_Base(Column=1, Title='Read Image Data', /Base_Align_Center) ELSE $
      tlb = Widget_Base(Column=1, Title='Read Image Data', Modal=1, $
         Group_Leader=group_leader, /Base_Align_Center)

   frameBase = Widget_Base(tlb, Frame=1, Column=1)

   ; Create the directory widgets.
   dirnamebase = Widget_Base(frameBase, Row=1)
      dirnamelabel = Widget_Label(dirnamebase, Value='Directory:')
      dirnameID = Widget_Text(dirnamebase, Value=startDirectory, /Editable, $
         Event_Pro='GETIMAGE_NULL_EVENTS', XSize=Fix(2.0*StrLen(startDirectory) > 50))

   ; Create the filename widgets.
   filenamebase = Widget_Base(frameBase, Row=1)
      filenamelabel = Widget_Label(filenamebase, Value='Filename:')
      filenameID = Widget_Text(filenamebase, Value=filename, /Editable, $
         Event_Pro='GETIMAGE_NULL_EVENTS', XSize=2*StrLen(filename) > 20)

   ; Create a button to allow user to pick a filename.
   pickbutton = Widget_Button(filenamebase, Value='Pick Filename')

   ; Create a droplist widget to select file data types.
   database = Widget_Base(frameBase, Row=1)
      datatypes = ['Byte', 'Integer (16-bit)', 'Long Integer (32-bit)', 'Float', 'Double', 'Unsigned Integer', 'Unsigned Long', '64-bit Long', '64-bit Unsigned Long']
      types = [1, 2, 3, 4, 5, 12, 13, 14, 15]
      theIndex = Where(types EQ datatype, count)
      datatypesID = Widget_Droplist(database, Value=datatypes, UValue=types, $
         Title='Data Type: ', Event_Pro='GETIMAGE_NULL_EVENTS')
      IF count EQ 0 THEN theIndex = 0
      Widget_Control, datatypesID, Set_Droplist_Select=theIndex

   ; Create a droplist widget to select file formats.
   formatlistID = Widget_Droplist(database, Value=['Unformatted', 'XDR format'], $
      Title='File Format: ', Event_Pro='GETIMAGE_XDR_EVENTS')


   row4 = Widget_Base(frameBase, Row=1)
   dimsID = Widget_Droplist(row4, Title='Image Dimensions: ', Value=StrTrim(Indgen(7)+1, 2), $
      Event_PRO = 'GETIMAGE_DIMS_EVENTS')
   Widget_Control, dimsID, Set_Droplist_Select=1

   byteorderID = Widget_Droplist(row4, Title='Image Byte Order: ', $
      Value=['Native', 'Big Endian', 'Little Endian'], Event_PRO = 'GETIMAGE_NULL_EVENTS')
   Widget_Control, byteorderID, Set_Droplist_Select=endian

   label = Widget_Label(row4, Value=' Header Size: ')
   combovalues = ['0','32','64','128', '256', '512','1024']
   IF header GT 0 THEN combovalues = [StrTrim(header,2), combovalues]
   headerID = Widget_Combobox(row4, Value=combovalues, $
      /Editable, Event_PRO = 'GETIMAGE_NULL_EVENTS')


   ; Create widgets to gather the required file sizes.

   sizebase = Widget_Base(frameBase, Row=2)

   d = IntArr(8)
   d[0] = FSC_Field(sizebase, Title='1st Dim:', Value=0, /Integer, XSize=6, /Positive)
   d[1] = FSC_Field(sizebase, Title='2nd Dim:', Value=0, /Integer, XSize=6, /Positive)
   d[2] = FSC_Field(sizebase, Title='3rd Dim:', Value=0, /Integer, XSize=6, /Positive)
   d[3] = FSC_Field(sizebase, Title='4th Dim:', Value=0, /Integer, XSize=6, /Positive)
   d[4] = FSC_Field(sizebase, Title='5th Dim:', Value=0, /Integer, XSize=6, /Positive)
   d[5] = FSC_Field(sizebase, Title='6th Dim:', Value=0, /Integer, XSize=6, /Positive)
   d[6] = FSC_Field(sizebase, Title='7th Dim:', Value=0, /Integer, XSize=6, /Positive)
   d[7] = FSC_Field(sizebase, Title='8th Dim:', Value=0, /Integer, XSize=6, /Positive)

   num = 1 > Size(dimensions, /N_Elements) < 8
   FOR j=0,num-1 DO Widget_Control, d[j], Set_Value=dimensions[j]
   FOR j=num, 7 DO Widget_Control, d[j], Sensitive=0

   ; Create cancel and accept buttons.
   cancelbase = Widget_Base(tlb, Row=1)
      cancel = Widget_Button(cancelbase, Value='Cancel')
      accept = Widget_Button(cancelbase, Value='Accept')

   ; Recalculate the length of the filenameID widget.
   g1 = Widget_Info(dirnamebase, /Geometry)
   g2 = Widget_Info(filenamelabel, /Geometry)
   g3 = Widget_Info(pickbutton, /Geometry)
   target = g1.scr_xsize - (g2.scr_xsize + g3.scr_xsize) - 10
   Widget_Control, filenameID, Scr_XSize=target

   ; Center the program on the display.
   cgCenterTLB, tlb

   ; Realize the program.
   Widget_Control, tlb, /Realize

   ; Create a pointer to store the information collected from the form.
   ; The initial data stored here is set to CANCEL, so nothing needs to
   ; be done if the user kills the widget with the mouse.
   ptrToFormData = Ptr_New({cancel:1})

   ; Set the correct file format in the format droplist widget.
   Widget_Control, formatlistID, Set_Droplist_Select=Keyword_Set(xdr)

   ; Set the text insertion point at the end of the filename text widget.
   tip = [StrLen(filename),0]
   Widget_Control, filenameID, Input_Focus=1
   Widget_Control, filenameID, Set_Text_Select=tip

   ; Create an info structure with program information.
   info = { filenameID:filenameID, $        ; The name of the file.
            d:d, $                          ; The dimension widgets IDs.
            dimsID:dimsID, $                ; The number of dimensions widget ID.
            byteorderID:byteorderID, $      ; The byte order widget ID.
            dirnameID:dirnameID, $          ; The ID of the widget with the directory name.
            headerID:headerID, $            ; The ID of the widget with the image HEADER size.
            datatypesID:datatypesID, $      ; The ID of the image data TYPE droplist ID.
            formatlistID:formatlistID, $    ; The ID of the image FORMAT droplist ID.
            datatypes:types, $              ; The possible data types.
            ptrToFormData:ptrToFormData}    ; A pointer to store the form information.

  ; Store the info structure in the user value of the top-level base.
   Widget_Control, tlb, Set_UValue=info

   ; The form will be a MODAL or BLOCKING widget, depending upon the
   ; presence of the PARENT. We block or are MODAL here until the widget
   ; is destroyed.
   XManager, 'getimage', tlb, Event_Handler='GETIMAGE_EVENT'

   ; When the widget is destroyed, the block is released, and we
   ; return here. Get the form data that was collected by the form
   ; and stored in the pointer location.
   formdata = *ptrToFormData

   ; If there is nothing here. Free the pointer and return.
   IF N_Elements(formdata) EQ 0 THEN BEGIN
      Ptr_Free, ptrToFormData
      canceled = 1
      RETURN, 0
   ENDIF

   ; Did the user cancel out of the form? If so, return a 0.
   IF formdata.cancel EQ 1 THEN BEGIN
      Ptr_Free, ptrToFormData
      canceled = 1
      RETURN, 0
   ENDIF

   ; Make the proper sized image array.
   image = Make_Array(Dimension=formdata.dimensions, Type=formdata.type)

   ; We can have all kinds of trouble reading data. Let's catch all
   ; input and output errors and alert user without crashing the program!
   Catch, theError
   IF theError NE 0 THEN BEGIN

      ; If we can't read the file for some reason, let the user know
      ; why, free the pointer and its information, check the logical
      ; unit number back in if it is checked out, and return a 0.
      ok = cgErrorMsg()
      Ptr_Free, ptrToFormData
      canceled = 1
      IF N_Elements(lun) NE 0 THEN Free_Lun, lun
      RETURN, 0
   ENDIF

   ; Set the canceled flag.
   canceled = formdata.cancel

   ; Read the data file.
   IF formdata.header GT 0 THEN headdata = BytArr(formdata.header)
   Get_Lun, lun
   CASE formdata.endian OF
      0: OpenR, lun, formdata.filename, XDR=formdata.xdr
      1: OpenR, lun, formdata.filename, XDR=formdata.xdr, /Swap_If_Little_Endian
      2: OpenR, lun, formdata.filename, XDR=formdata.xdr, /Swap_If_Big_Endian
   ENDCASE
   IF formdata.header GT 0 THEN ReadU, lun, headdata
   ReadU, lun, image
   Free_Lun, lun

   ; Free the pointer.
   Ptr_Free, ptrToFormData

   RETURN, image

END ; of GETIMAGE program ***************************************************
