pro MODFITS, filename, data, header, EXTEN_NO = exten_no, ERRMSG = errmsg, $ EXTNAME = extname ;+ ; NAME: ; MODFITS ; PURPOSE: ; Modify a FITS file by updating the header and/or data array. ; EXPLANATION: ; Update the data and/or header in a specified FITS extension or primary ; HDU. ; ; The size of the supplied FITS header or data array does not ; need to match the size of the existing header or data array. ; ; CALLING SEQUENCE: ; MODFITS, Filename_or_fcb, Data, [ Header, EXTEN_NO =, EXTNAME= , ERRMSG=] ; ; INPUTS: ; FILENAME/FCB = Scalar string containing either the name of the FITS file ; to be modified, or the IO file control block returned after ; opening the file with FITS_OPEN,/UPDATE. The explicit ; use of FITS_OPEN can save time if many extensions in a ; single file will be updated. ; ; DATA - data array to be inserted into the FITS file. Set DATA = 0 ; to leave the data portion of the FITS file unmodified. Data ; can also be an IDL structure (e.g. as returned by MRDFITS). ; provided that it does not include IDL pointers. ; ; HEADER - FITS header (string array) to be updated in the FITS file. ; ; OPTIONAL INPUT KEYWORDS: ; A specific extension can be specified with either the EXTNAME or ; EXTEN_NO keyword ; ; EXTEN_NO - scalar integer specifying the FITS extension to modified. For ; example, specify EXTEN = 1 or /EXTEN to modify the first ; FITS extension. ; EXTNAME - string name of the extension to modify. ; ; ; OPTIONAL OUTPUT KEYWORD: ; ERRMSG - If this keyword is supplied, then any error mesasges will be ; returned to the user in this parameter rather than depending on ; on the MESSAGE routine in IDL. If no errors are encountered ; then a null string is returned. ; ; EXAMPLES: ; (1) Modify the value of the DATE keyword in the primary header of a ; file TEST.FITS. ; ; IDL> h = headfits('test.fits') ;Read primary header ; IDL> sxaddpar,h,'DATE','2015-03-23' ;Modify value of DATE ; IDL> modfits,'test.fits',0,h ;Update header only ; ; (2) Replace the values of the primary image array in 'test.fits' with ; their absolute values ; ; IDL> im = readfits('test.fits') ;Read image array ; IDL> im = abs(im) ;Take absolute values ; IDL> modfits,'test.fits',im ;Update image array ; ; (3) Add some HISTORY records to the FITS header in the first extension ; of a file 'test.fits' ; ; IDL> h = headfits('test.fits',/ext) ;Read first extension hdr ; IDL> sxaddhist,['Comment 1','Comment 2'],h ; IDL> modfits,'test.fits',0,h,/ext ;Update extension hdr ; ; (4) Change 'OBSDATE' keyword to 'OBS-DATE' in every extension in a ; FITS file. Explicitly open with FITS_OPEN to save compute time. ; ; fits_open,'test.fits',io,/update ;Faster to explicity open ; for i = 1,io.nextend do begin ;Loop over extensions ; fits_read,io,0,h,/header_only,exten_no=i,/No_PDU ;Get header ; date= sxpar(h,'OBSDATE') ;Save keyword value ; sxaddpar,h,'OBS-DATE',date,after='OBSDATE' ; sxdelpar,h,'OBSDATE' ;Delete bad keyword ; modfits,io,0,h,exten_no=i ;Update header ; endfor ; ; Note the use of the /No_PDU keyword in the FITS_READ call -- one ; does *not* want to append the primary header, if the STScI ; inheritance convention is adopted. ; ; NOTES: ; Uses the BLKSHIFT procedure to shift the contents of the FITS file if ; the new data or header differs in size by more than 2880 bytes from the ; old data or header. If a file control block (FCB) structure is ; supplied, then the values of START_HEADER, START_DATA and NBYTES may ; be modified if the file size changes. ; ; Also see the procedures FXHMODIFY to add a single FITS keyword to a ; header in a FITS files, and FXBGROW to enlarge the size of a binary ; table. ; ; RESTRICTIONS: ; (1) Cannot be used to modify the data in FITS files with random ; groups or variable length binary tables. (The headers in such ; files *can* be modified.) Cannot be used to modify individual ; columns in binary or ASCII tables. ; ; (2) If a data array but no FITS header is supplied, then MODFITS does ; not check to make sure that the existing header is consistent with ; the new data. ; ; (3) Does not work with compressed files ; ; (4) The Checksum keywords will not be updated if the array to be ; updated is supplied as a structure (e.g. from MRDFITS). ; PROCEDURES USED: ; Functions: N_BYTES(), SXPAR() ; Procedures: BLKSHIFT, CHECK_FITS, FITS_OPEN, FITS_READ. SETDEFAULTVALUE ; ; MODIFICATION HISTORY: ; Written, Wayne Landsman December, 1994 ; Fixed possible problem when using WRITEU after READU October 1997 ; New and old sizes need only be the same within multiple of 2880 bytes ; Added call to IS_IEEE_BIG() W. Landsman May 1999 ; Added ERRMSG output keyword W. Landsman May 2000 ; Update tests for incompatible sizes W. Landsman December 2000 ; Major rewrite to use FITS_OPEN procedures W. Landsman November 2001 ; Add /No_PDU call to FITS_READ call W. Landsman June 2002 ; Update CHECKSUM keywords if already present in header, add padding ; if new data size is smaller than old W.Landsman December 2002 ; Only check XTENSION value if EXTEN_NO > 1 W. Landsman Feb. 2003 ; Correct for unsigned data on little endian machines W. Landsman Apr 2003 ; Major rewrite to allow changing size of data or header W.L. Aug 2003 ; Fixed case where updated header exactly fills boundary W.L. Feb 2004 ; More robust error reporting W.L. Dec 2004 ; Make sure input header ends with a END W.L. March 2006 ; Assume since V5.5, remove VMS support, assume FITS_OPEN will ; perform byte swapping W.L. Sep 2006 ; Update FCB structure if file size changes W.L. March 2007 ; Fix problem when data size must be extended W.L. August 2007 ; Don't assume supplied FITS header is 80 bytes W. L. Dec 2007 ; Check for new END position after adding CHECKSUM W.L. July 2008 ; Added EXTNAME input keyword W.L. July 2008 ; Allow data to be an IDL structure A. Conley/W.L. June 2009 ; Use V6.0 notation, add /NOZERO to BLKSHIFT W.L. Feb 2011 ; Don't try to update Checksums when structure supplied W.L. April 2011 ; Allow structure with only 1 element W.L. Feb 2012 ; Don't require that a FITS header is supplied W.L. Feb 2016 ; Use CATCH rather than ON_IOError, 2 W. L July 2018 ; Fix when no header supplied W.L. June 2022 ;- compile_opt idl2 ; Check for filename input if N_params() LT 1 then begin print,'Syntax - ' + $ 'MODFITS, Filename, Data, [ Header, EXTEN_NO=, EXTNAME=, ERRMSG= ]' return endif Catch, theError if theError NE 0 then begin Catch,/Cancel void = cgErrorMsg(/quiet) return endif setdefaultvalue, exten_no, 0 setdefaultvalue, Header, 0 nheader = N_elements(Header) updated = 0b ;Make sure END statement is the last line in supplied FITS header if nheader GT 1 then begin endline = where( strmid(Header,0,8) EQ 'END ', Nend) if Nend EQ 0 then begin message,/INF, $ 'WARNING - An END statement has been appended to the FITS header' Header = [ Header, 'END' + string( replicate(32b,77) ) ] endif else header = header[0:endline] endif ndata = N_elements(data) dtype = size(data,/TNAME) printerr = ~arg_present(ERRMSG) fcbsupplied = size(filename,/TNAME) EQ 'STRUCT' if (nheader GT 1) && (ndata GT 1) && (dtype NE 'STRUCT') then begin check_fits, data, header, ERRMSG = MESSAGE if message NE '' then goto, BAD_EXIT endif ; Open file and read header information if (exten_no EQ 0) && (~keyword_set(EXTNAME)) then begin if nheader GT 1 then $ if strmid( header[0], 0, 8) NE 'SIMPLE ' then begin message = $ 'Input header does not contain required SIMPLE keyword' goto, BAD_EXIT endif endif else begin if nheader GT 1 then $ if strmid( header[0], 0, 8) NE 'XTENSION' then begin message = $ 'Input header does not contain required XTENSION keyword' goto, BAD_EXIT endif endelse ; Was a file name or file control block supplied? if ~fcbsupplied then begin fits_open, filename, io,/update,/No_Abort,message=message if message NE '' then GOTO, BAD_EXIT endif else begin if filename.open_for_write EQ 0 then begin message = 'FITS file is set for READONLY, cannot be updated' goto, BAD_EXIT endif io = filename endelse ; Getting starting position of data and header if keyword_set(extname) then begin exten_no = where( strupcase(io.extname) EQ strupcase(extname), Nfound) if Nfound EQ 0 then begin message,'Extension name ' + extname + ' not found in FITS file' GOTO, BAD_EXIT endif endif unit = io.unit start_d = io.start_data[exten_no] if exten_no NE io.nextend then begin start_h = io.start_header[exten_no+1] nbytes = start_h - start_d endif else nbytes = N_BYTES(data) FITS_READ,Io,0,oldheader,/header_only, exten=exten_no, /No_PDU, $ message = message,/no_abort if message NE '' then goto, BAD_EXIT dochecksum = sxpar(oldheader,'CHECKSUM', Count = N_checksum) checksum = N_checksum GT 0 ; Update header, including any CHECKSUM keywords if present if nheader GT 1 then begin noldheader = start_d - io.start_header[exten_no] if dtype EQ 'UINT' then $ sxaddpar,header,'BZERO',32768,'Data is unsigned integer' if dtype EQ 'ULONG' then $ sxaddpar,header,'BZERO',2147483648,'Data is unsigned long' if checksum then begin if (Ndata GT 1) && (dtype NE 'STRUCT') then $ FITS_ADD_CHECKSUM, header, data else $ FITS_ADD_CHECKSUM, header endif ; Position of 'END' card may have changed - Bug fix July 2008 endline = where( strmid(Header,0,8) EQ 'END ', Nend) newbytes = N_elements(header)*80 block = (newbytes-1)/2880 - (Noldheader-1)/2880 if block NE 0 then begin BLKSHIFT, io.unit, start_d, block*2880L, /NOZERO start_d += block*2880L io.start_data[exten_no:*] += block*2880L io.nbytes += block*2880L if exten_no NE io.nextend then begin start_h += block*2880L io.start_header[exten_no+1:*] += block*2880L endif endif point_lun, unit, io.start_header[exten_no] ;Position header start bhdr = replicate(32b, newbytes) for n = 0l, endline[0] do bhdr[80*n] = byte( header[n] ) writeu, unit, bhdr remain = newbytes mod 2880 if remain GT 0 then writeu, unit, replicate( 32b, 2880 - remain) updated = 1b endif if (ndata GT 1) || (dtype EQ 'STRUCT') then begin newbytes = N_BYTES(data) ;total number of bytes in supplied data block = (newbytes-1)/2880 - (nbytes-1)/2880 if (block NE 0) && (exten_no NE io.nextend) then begin BLKSHIFT, io.unit, start_h, block*2880L,/NOZERO io.nbytes += block*2880L io.start_header[exten_no+1:*] += block*2880L io.start_data[exten_no+1:*] += block*2880L endif if (nheader EQ 0) && (dtype NE 'STRUCT') then begin check_fits,data,oldheader,ERRMSG = message if message NE '' then goto, BAD_EXIT endif junk = fstat(unit) ;Need this before changing from READU to WRITEU point_lun, unit, start_d if dtype EQ 'UINT' then newdata = fix(data - 32768) if dtype EQ 'ULONG' then newdata = long(data - 2147483648) if N_elements(newdata) GT 0 then writeu, unit, newdata else $ writeu, unit ,data remain = newbytes mod 2880 if remain GT 0 then begin padnum = 0b if exten_no GT 0 then begin exten = sxpar( oldheader, 'XTENSION') if exten EQ 'TABLE ' then padnum = 32b endif writeu, unit, replicate( padnum, 2880 - remain) endif updated = 1b endif if ~fcbsupplied then FITS_CLOSE,io else filename = io if ~updated then message,'FITS file not modified',/INF return BAD_EXIT: if N_elements(io) GT 0 then if ~fcbsupplied then fits_close,io if printerr then message,'ERROR - ' + message,/CON else errmsg = message if fcbsupplied then fname = filename.filename else fname = filename message,'FITS file ' + fname + ' not modified',/INF return end