PRO FXWRITE, FILENAME, HEADER, DATA, NANVALUE=NANVALUE, $ NOUPDATE=NOUPDATE, ERRMSG=ERRMSG, APPEND=APPEND, $ ALLOW_DEGEN=ALLOW_DEGEN ;+ ; NAME: ; FXWRITE ; Purpose : ; Write a disk FITS file. ; Explanation : ; Creates or appends to a disk FITS file and writes a FITS ; header, and optionally an image data array. ; Use : ; FXWRITE, FILENAME, HEADER [, DATA ] ; Inputs : ; FILENAME = String containing the name of the file to be written. ; HEADER = String array containing the header for the FITS file. ; Opt. Inputs : ; DATA = IDL data array to be written to the file. If not passed, ; then it is assumed that extensions will be added to the ; file. ; Outputs : ; None. ; Opt. Outputs: ; None. ; Keywords : ; NANVALUE = Value signalling data dropout. All points corresponding to ; this value are set to be IEEE NaN (not-a-number). Ignored ; unless DATA is of type float, double-precision or complex. ; NOUPDATE = If set, then the optional BSCALE and BZERO keywords in the ; HEADER array will not be changed. The default is to reset ; these keywords to BSCALE=1, BZERO=0. ; APPEND = If set, then an existing file will be appended to. ; Appending to a non-existent file will create it. If ; a primary HDU already exists then it will be modified ; to have EXTEND = T. ; ALLOW_DEGEN = If set, then don't check for degenerate axes in ; CHECK_FITS. ; ERRMSG = If defined and passed, then any error messages will be ; returned to the user in this parameter rather than ; depending on the MESSAGE routine in IDL. If no errors are ; encountered, then a null string is returned. In order to ; use this feature, ERRMSG must be defined first, e.g. ; ; ERRMSG = '' ; FXWRITE, ERRMSG=ERRMSG, ... ; IF ERRMSG NE '' THEN ... ; ; Calls : ; CHECK_FITS, GET_DATE, FXADDPAR, FXPAR ; Common : ; None. ; Restrictions: ; If DATA is passed, then HEADER must be consistent with it. If no data ; array is being written to the file, then HEADER must also be consistent ; with that. The routine FXHMAKE can be used to create a FITS header. ; ; If found, then the optional keywords BSCALE and BZERO in the HEADER ; array is changed so that BSCALE=1 and BZERO=0. This is so that these ; scaling parameters are not applied to the data a second time by another ; routine. Also, history records are added storing the original values ; of these constants. (Other values of BZERO are used for unsigned ; integers.) ; ; If the /NOUPDATE keyword is set, however, then the BSCALE and BZERO ; keywords are not changed. The user should then be aware that FITS ; readers will apply these numbers to the data, even if the data is ; already converted to floating point form. ; ; Groups are not supported. ; ; Side effects: ; HEADER may be modified. One way it may be modified is describe ; above under NOUPDATE. The first header card may also be ; modified to conform to the FITS standard if it does not ; already agree (i.e. use of either the SIMPLE or XTENSION ; keyword depending on whether the image is the primary HDU or ; not). ; Category : ; Data Handling, I/O, FITS, Generic. ; Prev. Hist. : ; W. Thompson, Jan 1992, from WRITEFITS by J. Woffard and W. Landsman. ; Differences include: ; ; * Made DATA array optional, and HEADER array mandatory. ; * Changed order of HEADER and DATA parameters. ; * No attempt made to fix HEADER array. ; ; W. Thompson, May 1992, changed open statement to force 2880 byte fixed ; length records (VMS). The software here does not ; depend on this file configuration, but other ; FITS readers might. ; W. Thompson, Aug 1992, added code to reset BSCALE and BZERO records, ; and added the NOUPDATE keyword. ; Written : ; William Thompson, GSFC, January 1992. ; Modified : ; Version 1, William Thompson, GSFC, 12 April 1993. ; Incorporated into CDS library. ; Version 2, William Thompson, GSFC, 31 May 1994 ; Added ERRMSG keyword. ; Version 3, William Thompson, GSFC, 23 June 1994 ; Modified so that ERRMSG is not touched if not defined. ; Version 4, William Thompson, GSFC, 12 August 1999 ; Catch error if unable to open file. ; Version 4.1 Wayne Landsman, GSFC, 02 May 2000 ; Remove !ERR in call to CHECK_FITS, Use ARG_PRESENT() ; Version 5, William Thompson, GSFC, 22 September 2004 ; Recognize unsigned integer types ; Version 5.1 W. Landsman 14 November 2004 ; Allow for need for 64bit number of bytes ; Version 6, Craig Markwardt, GSFC, 30 May 2005 ; Ability to append to existing files ; Version 7, W. Landsman GSFC, Mar 2014 ; Remove HOST_TO_IEEE, Use V6.0 notation ; Version 8, William Thompson, 26-Jun-2019, add /ALLOW_DEGEN ; Version : ; Version 8, 26-Jun-2019 ;- ; ON_ERROR, 2 ; ; Check the number of parameters. ; IF N_PARAMS() LT 2 THEN BEGIN MESSAGE = 'Syntax: FXWRITE, FILENAME, HEADER [, DATA ]' GOTO, HANDLE_ERROR ENDIF ; ; Check the header against the data being written to the file. If the data ; array is not passed, then NAXIS should be set to zero, and EXTEND should be ; true. ; IF N_PARAMS() EQ 2 THEN BEGIN IF (FXPAR(HEADER,'NAXIS') NE 0) THEN BEGIN MESSAGE = 'NAXIS should be zero for no primary data array' GOTO, HANDLE_ERROR END ELSE IF (~FXPAR(HEADER,'EXTEND')) THEN BEGIN MESSAGE = 'EXTEND should be true for no primary data array' GOTO, HANDLE_ERROR ENDIF END ELSE BEGIN CHECK_FITS, DATA, HEADER, ERRMSG=MESSAGE, ALLOW_DEGEN=ALLOW_DEGEN IF MESSAGE NE '' THEN GOTO, HANDLE_ERROR ENDELSE ; ; Set the BSCALE and BZERO keywords to their default values. ; SZ = SIZE(DATA) TYPE = SZ[SZ[0]+1] IF N_PARAMS() EQ 3 THEN NEWDATA = DATA IF ~KEYWORD_SET(NOUPDATE) THEN BEGIN BZERO = FXPAR(HEADER,'BZERO') BSCALE = FXPAR(HEADER,'BSCALE') GET_DATE,DTE IF (BSCALE NE 0) AND (BSCALE NE 1) THEN BEGIN FXADDPAR,HEADER,'BSCALE',1. FXADDPAR,HEADER,'HISTORY',DTE+' reset BSCALE, was '+ $ STRTRIM(BSCALE,2) ENDIF ; ; If an unsigned data type then redefine BZERO to allow all the data to be ; stored in the file. ; BZERO0 = 0 IF (TYPE EQ 12) && (~KEYWORD_SET(NOUPDATE)) THEN BEGIN BZERO0 = '8000'X NEWDATA = FIX(TEMPORARY(NEWDATA) - BZERO) ENDIF IF (TYPE EQ 13) && (~KEYWORD_SET(NOUPDATE)) THEN BEGIN BZERO0 = '80000000'X NEWDATA = LONG(TEMPORARY(NEWDATA) - BZERO) ENDIF IF BZERO NE BZERO0 THEN BEGIN FXADDPAR,HEADER,'BZERO',BZERO0 FXADDPAR,HEADER,'HISTORY',DTE+' reset BZERO, was '+ $ STRTRIM(BZERO,2) ENDIF ENDIF ; ; Get the UNIT number, and open the file. ; GET_LUN, UNIT OPENW, UNIT, FILENAME, 2880, /BLOCK, ERROR=ERR, APPEND=APPEND VERB = 'creating' IF KEYWORD_SET(APPEND) THEN VERB = 'appending to' IF ERR NE 0 THEN BEGIN MESSAGE = 'Error '+VERB+' file '+FILENAME GOTO, HANDLE_ERROR ENDIF ; ; Special processing is required when we are appending to ; the file, to ensure that the FITS standards are met. ; (i.e. primary HDU must have EXTEND = T, and the header ; to be written must have XTENSION = 'IMAGE'). ; POINT_LUN, -UNIT, POS IF POS GT 0 THEN BEGIN ;; Release the file and call FXHMODIFY to edit the ;; header of the primary HDU. It is required to have ;; EXTEND=T. FXHMODIFY calls FXADDPAR, which ;; automatically places the EXTEND keyword in the ;; required position. FREE_LUN, UNIT FXHMODIFY, FILENAME, ERRMSG=MESSAGE, $ ; (EXTENSION=0 implied) 'EXTEND', 'T', ' FITS dataset may contain extensions' IF MESSAGE NE '' THEN GOTO, HANDLE_ERROR ;; Re-open the file GET_LUN, UNIT OPENW, UNIT, FILENAME, 2880, /BLOCK, ERROR=ERR, APPEND=APPEND IF ERR NE 0 THEN BEGIN MESSAGE = 'Error re-opening file '+FILENAME GOTO, HANDLE_ERROR ENDIF ;; Revise the header so that it begins with an ;; XTENSION keyword... if it doesn't already IF STRMID(HEADER[0], 0, 9) EQ 'SIMPLE =' THEN BEGIN ;; Extra work to preserve the comment DUMMY = FXPAR(HEADER, 'SIMPLE', COMMENT=COMMENT) FXADDPAR, DUMMYHEADER, 'XTENSION', 'IMAGE', COMMENT HEADER[0] = DUMMYHEADER[0] ENDIF ;; Find last NAXIS* keyword, since PCOUNT/GCOUNT follow them NAXIS = FXPAR(HEADER, 'NAXIS', COUNT=COUNT_NAXIS) IF NAXIS[0] GT 0 THEN PCOUNT_AFTER='NAXIS'+strtrim(NAXIS[0],2) ;; Required PCOUNT/GCOUNT keywords for following extensions FXADDPAR, HEADER, 'PCOUNT', 0, ' number of random group parameters', $ AFTER=PCOUNT_AFTER FXADDPAR, HEADER, 'GCOUNT', 1, ' number of random groups', $ AFTER='PCOUNT' ENDIF ELSE BEGIN ;; In the off chance that this header was used before to ;; write a header with XTENSION, make sure this *new* file ;; has SIMPLE = T IF STRMID(HEADER[0], 0, 9) EQ 'XTENSION=' THEN BEGIN ;; Extra work to preserve the comment DUMMY = FXPAR(HEADER, 'XTENSION', COMMENT=COMMENT) FXADDPAR, DUMMYHEADER, 'SIMPLE', 'T', COMMENT HEADER[0] = DUMMYHEADER[0] ENDIF ENDELSE ; ; Determine if an END line occurs, and add one if necessary ; ENDLINE = WHERE( STRMID(HEADER,0,8) EQ 'END ', NEND) ENDLINE = ENDLINE[0] IF NEND EQ 0 THEN BEGIN MESSAGE, 'WARNING - An END statement has been appended ' + $ 'to the FITS header', /INFORMATIONAL HEADER = [HEADER, 'END' + STRING(REPLICATE(32B,77))] ENDLINE = N_ELEMENTS(HEADER) - 1 ENDIF NMAX = ENDLINE + 1 ;Number of 80 byte records NHEAD = FIX((NMAX+35)/36) ;Number of 2880 byte records ; ; Convert to byte and force into 80 character lines ; BHDR = REPLICATE(32B, 80, 36*NHEAD) FOR N = 0,ENDLINE DO BHDR[0,N] = BYTE( STRMID(HEADER[N],0,80) ) WRITEU, UNIT, BHDR ; ; If passed, then write the data array. ; IF N_PARAMS() EQ 3 THEN BEGIN ; ; If necessary, then byte-swap the data before writing it out. Also, replace ; any values corresponding data dropout with IEEE NaN. ; IF (N_ELEMENTS(NANVALUE) EQ 1) && (TYPE GE 4) && $ (TYPE LE 6) THEN BEGIN W = WHERE(DATA EQ NANVALUE, COUNT) CASE TYPE OF 4: NAN = FLOAT( REPLICATE('FF'XB,4),0,1) 5: NAN = DOUBLE( REPLICATE('FF'XB,8),0,1) 6: NAN = COMPLEX(REPLICATE('FF'XB,8),0,1) 9: NAN = DCOMPLEX(REPLICATE('FF'XB,16),0,1) ENDCASE END ELSE COUNT = 0 ; SWAP_ENDIAN_INPLACE, NEWDATA, /SWAP_IF_LITTLE IF COUNT GT 0 THEN NEWDATA[W] = NAN ; WRITEU,UNIT,NEWDATA ; ; If necessary, then pad out to an integral multiple of 2880 bytes. ; BITPIX = FXPAR( HEADER, 'BITPIX' ) NBYTES = LONG64(N_ELEMENTS(DATA)) * (ABS(BITPIX) / 8 ) NPAD = NBYTES MOD 2880 IF NPAD NE 0 THEN BEGIN NPAD = 2880 - NPAD WRITEU,UNIT,BYTARR(NPAD) ENDIF ENDIF ; ; Close the file and return. ; FREE_LUN, UNIT IF ARG_PRESENT(ERRMSG) THEN ERRMSG = '' RETURN ; HANDLE_ERROR: IF N_ELEMENTS(UNIT) EQ 1 THEN FREE_LUN, UNIT IF ARG_PRESENT(ERRMSG) THEN ERRMSG = 'FXWRITE: ' + MESSAGE $ ELSE MESSAGE, MESSAGE ; END