;+ ; IO_SDFITS is the base class for spectral line and continuum sdfits classes. All the GENERAL functionality for reading, writing, navigating sdfits files, and for; translating their info to data containers is placed in this class. See UML for all IO Classes, or IO_SDFITS UML for just the line and continuum sdfits classes. ; ; @field file_path The path in which index and sdfits files are to be found ; @field fits_files An array of pointers to objects, one for each sdfits file ; @field index Object which manages the index file ; @field index_synced Flag which signals wether index and fits files are in sync ; @field update_expanded_files Flag determining wether index files are updated to keep in sink with an expanded fits file ; @field one_file Flag for determining if this object is locked into working with just one file ; @field Observer should not be here? ; @field tcal_table should not be here? ; @field backend should not be here? ; @field debug Flag that determines the verbosity of object ; ; ; @file_comments ; IO_SDFITS is the base class for spectral line and continuum sdfits classes. All the general functionality for reading, writing, navigating sdfits files, and for; translating their info to data containers is placed in this class. See UML for all IO Classes, or IO_SDFITS UML for just the line and continuum sdfits classes. ; ; @inherits io ; ; @version $Id$ ;- PRO io_sdfits__define compile_opt idl2, hidden io = { io_sdfits, inherits io, $ file_path:string(replicate(32B,256)), $ fits_files:ptr_new(), $ index:obj_new(), $ flags:obj_new(), $ index_synced:0L, $ update_expanded_files:0L, $ one_file:0L, $ observer:string(replicate(32B,256)), $ tcal_table:string(replicate(32B,256)), $ backend:string(replicate(32B,256)), $ last_record:0L, $ online:0L, $ update_in_progress:0L, $ online_status:obj_new(), $ debug:0L $ } END ;+ ; Class Constructor. Initialize the object ; ; @uses IO::init ; ; @private ;- FUNCTION IO_SDFITS::init compile_opt idl2 r = self->IO::init() self.flags = obj_new("flags") self.last_record = -1 return, r END ;+ ; Class cleanup on deletion: cleans up index object and sdfits objects ; ; @private ;- PRO IO_SDFITS::cleanup compile_opt idl2 if (obj_valid(self.index) eq 1) then obj_destroy, self.index if (obj_valid(self.flags) eq 1) then obj_destroy, self.flags self->free_fits_objs END ;+ ; The class can be made to verbosly describe what its doing ;- PRO IO_SDFITS::set_debug_on compile_opt idl2 self.debug = 1 if obj_valid(self.index) then self.index->set_debug_on if obj_valid(self.flags) then self.flags->set_debug_on self->set_debug_for_fits_objs, 1 END ;+ ; The class can be made to be quite ;- PRO IO_SDFITS::set_debug_off compile_opt idl2 self.debug = 0 if obj_valid(self.index) then self.index->set_debug_off if obj_valid(self.flags) then self.flags->set_debug_off self->set_debug_for_fits_objs, 0 END ;+ ; This method looks into the given directory and attempts to load any existing ; index file. If the file does not exist, all sdfits files in this directory ; are loaded using the add_file method, and a new index is created. For a complete ; description, see the flowchart. ; ; @uses add_file ; ; @param dir {in}{type=string} The path in which all sdfits files and possibly ; the index file are to be found ; ; @examples ;
; path = '/users/me/my_project'
; io = obj_new('io_sdfits_line')
; io->set_project, path
;
;-
PRO IO_SDFITS::set_project, dir, _EXTRA=ex
compile_opt idl2
if (self.one_file ne 0) then message, "this object is commited to using only one file"
if (self->file_exists(dir) eq 0) then begin
message, 'Cannot set project; directory does not exist'
return
endif
; parse the contents of the extra keywords
new_index = 0
if n_elements(ex) ne 0 then begin
tags = tag_names(ex)
for i=0,n_elements(tags)-1 do begin
if tags[i] eq "NEW_INDEX" then begin
if ex.(i) eq 1 then new_index = 1
endif
endfor
endif
; set location of all sdfits files
self->set_file_path, dir
file_paths = file_search(dir+'/*.fits')
if (self.debug eq 1) then print, file_paths
files = file_basename(file_paths)
; create the name of the index file based off the
parts = strsplit(dir, "/", /extract)
index_file_name = parts[n_elements(parts)-1]+".index"
self.index->set_file_name, index_file_name
; place the index file in this same dir
full_index_file_name = self.index->get_full_file_name()
if self.debug then print, "index_file_name: "+index_file_name
; read existing index file, or create a new one
if new_index then begin
; in this case ALL index files are rewritten
self->set_project_create_index_file, file_paths
endif else begin
if (self->file_exists(full_index_file_name) eq 0) then begin
; index doesn't exist, so create one using all files in path
for i=0,n_elements(files)-1 do begin
self->add_file,files[i]
endfor
endif else begin
; index exists use it
self.index->read_file, file_name=file_name, ver_status
; could not read index; version number too old
if ver_status eq 0 then begin
message, 'cannot load index; version number too old. Creating new index.',/info
self.index->reset
for i=0,n_elements(files)-1 do begin
if i eq 0 then begin
self->add_file,files[i],/new_index
endif else begin
self->add_file,files[i]
endelse
endfor
endif
; if the index info makes no sense, dont use it
if (self.index->check_index_with_reality() eq 0) then begin
message, 'cannot load pre-existing index; creating new index',/info
self.index->reset
for i=0,n_elements(files)-1 do begin
if i eq 0 then begin
self->add_file,files[i],/new_index
endif else begin
self->add_file,files[i]
endelse
endfor
return
endif
; create fits objs for files listed in index, if need be
self->conform_fits_objs_to_index
self.index_synced = 1
; add the additional files that might not be in this index already
index_files = self->get_index_files()
for i=0,n_elements(files)-1 do begin
cnt=0
c = where(files[i] eq index_files,cnt)
if (cnt eq 0) then begin
; this file isn't in the index file - add it
self->add_file,files[i]
endif
endfor
; index passes basic tests - load flags
self->load_index_flag_info
endelse ; if index exists
endelse ; if new_index keyword set
END
;+
; This method can be used to lock the io object into working with only one
; sdfits file. An index file is automatically created (overwrites pre-existing one).
; @uses add_file
; @param file_name {in}{type=string} The file name of the sdfits file (no path)
; @param status {out}{optional}{type=boolean} 1 - success, 0 - failure
;
; @keyword file_path {in}{optinal}{type=string} Where to find the sdfits file
; @keyword index_name {in}{optinal}{type=string}{default='file_name.index'} What to call the new index file
;
; @examples
;
; io = obj_new('io_sdfits_cntm')
; io->set_file, 'TREG_04_01.dcr.raw.fits'
;
;-
PRO IO_SDFITS::set_file, file_name, status, file_path=file_path, index_name=index_name, online=online, _EXTRA=ex
compile_opt idl2
status = 1
if (self.one_file ne 0) then message, "this object is commited to using only one file"
; online mode supercedes all other keywords
if keyword_set(online) then begin
self->set_online, file_name
return
endif
; discard all other fits objects
self->free_fits_objs
; see if file path is inlcuded seperately
if keyword_set(file_path) then begin
self->set_file_path, file_path
file_base=file_name
endif else begin
; see if file path is inlcuded in file name or not
if (strpos(file_name,'/') ne -1) then begin
self->set_file_path, file_dirname(file_name)
file_base = file_basename(file_name)
endif else begin
file_base = file_name
endelse
endelse
; index file name == to file, or keyword?
if keyword_set(index_name) then begin
index_file=index_name
endif else begin
;parts=strsplit(file_base,'.',/extract)
;index_file = strjoin(parts[0:n_elements(parts)-2],'.')+'.index'
index_file = self->get_index_name_from_fits_name(file_base)
endelse
self->set_index_file_name, index_file
; add file, loading or creating a new index according to keyword
self->add_file, file_base, status, _EXTRA=ex
if status ne 1 then return
; mark this io object as dedicated to one file
self.one_file = 1
END
;+
; This method is the main interface to the sdfits io classes. It is used in turn
; by set_project and set_file. Most of the logic for keeping the index file in
; sync with the fits files is coded in this method. For a complete description,
; see the flowchart.
;
; @param file_name {in}{type=string} Name of sdfits file to add (no path)
; @param status {out}{optional}{type=boolean} 1 - success, 0 - failure
;
; @keyword new_index {in}{optinal}{type=boolean}{default=0} Forces the creation
; of a new index file (overwrites pre-existing index)
;
; @examples
;
; path = '/users/me/my_project'
; io = obj_new('io_sdfits_line')
; io->set_file_path, path
; io->add_file, 'TREG_O1_04.acs.raw.fits'
;
;-
PRO IO_SDFITS::add_file, file_name, status, new_index=new_index, index_name=index_name, max_nrows=max_nrows
compile_opt idl2
status = 1
if (self.one_file ne 0) then message, "this object is commited to using only one file"
if keyword_set(new_index) then make_new_index=1 else make_new_index=0
if keyword_set(index_name) then self.index->set_file_name, index_name
; check file existence
if (self->file_exists(self->get_full_file_name(file_name)) eq 0 ) then begin
message, "Cannot add file that does not exist: "+file_name
status = 0
return
endif
; check fits object existence
fits_obj = self->get_fits(file_name)
if (obj_valid(fits_obj) eq 0) then begin
; create an interface object for this file'
self->add_fits_obj, file_name, result
if not result then begin
message, "Cannot add file: "+file_name,/info
status = 0
return
endif
fits_obj = self->get_fits(file_name)
endif else begin
; nothing will be done; not really an error
message, 'this file has already been added to this object',/info
return
endelse
; index must be brought in line with new fits file
self.index_synced = 0
; force a new index file?
if make_new_index then begin
; starting from scratch, we're done
self->create_index_file
return
endif
; is there an index file loaded?
if (self.index->is_file_loaded() eq 0) then begin
if self.debug then print, 'index not loaded'
; try to load up the named index file; file exists?
if(self.index->file_exists() eq 0) then begin
if self.debug then print, 'index does not exist'
if not self.online then begin
; index file does not exist, create it, we're done
self->create_index_file
return
endif else begin
message, "Cannot create index file: online directory is read-only.",/info
status = 0
return
endelse
endif else begin
if self.debug then print, 'index exists, reading'
; index file exists, load it
self.index->read_file, ver_status, max_nrows=max_nrows
; if version keyword is wrong, this index file could not be read.
if ver_status eq 0 then begin
if not self.online then begin
; we can recreate this index
print,"Index file is out of date ... updating to current version number."
self.index->reset
self->create_index_file
endif else begin
; we cant recreate an online index
print, "Index file is out of date: cannot connect to file in online directory."
status = 0
return
endelse
endif
; dont do this check in the online case
if not self.online then begin
; index file match reality? a fits file with larger extension is allowable
expanded = 0
index_matches = self.index->check_index_with_reality(expanded,/verbose)
if (index_matches eq 0) then begin
; something doesn't agree between the index file and reality
message, 'Cannot use this index file ... Creating new index file', /info
self.index->reset
self->create_index_file
return
endif
endif
; index passes basic tests - load flags
self->load_index_flag_info
endelse ; if file exists
endif ; if file loaded
; dont do any further checks if we are online
if not self.online then begin
; check this file is in index
files = self->get_index_files(/unique)
; is base file name in list of files in index?
count = 0
ind = where(fits_obj->get_file_name() eq files, count)
if (count eq 0) then begin
if self.debug then print, 'fits file not in index, updating index with it.'
; file is not in index, update the index file, we're done
self->update_index_with_fits_file, fits_obj
return
endif else begin
; file is in index, does index info match with fits info?
expanded = 0
file_info_matches = self.index->check_file_properties(fits_obj->get_file_name(),expanded,/verbose)
if file_info_matches then begin
if self.debug then print, 'fits file in index and properties match.'
; all extensions have same length, we're done
self.index_synced = 1
return
endif else begin
if self.debug then print, 'fits file in index but properties do not match.'
if expanded then begin
; fits extension too big, updating?
if self.update_expanded_files then begin
message, 'update the damn files',/info
endif else begin
; no -error
message, 'cannot use this index file; file has been expanded since index creation.'
status = 0
return
endelse
endif else begin
message, 'cannot use this index file; file properties do not match index.'
status = 0
return
endelse ; if file has been expanded since index creation
endelse ; if file properties match index
endelse ; if file found in index
endif else begin
; we're online - assume index is getting taken care of for us
self.index_synced = 1
endelse ; if online
status = 1
return
END
;+
; For every sdfits file managed by the current index file, the flags object
; is updated with info from these files.
;-
PRO IO_SDFITS::load_index_flag_info
compile_opt idl2, hidden
; get the file names
files = self.index->get_column_values("FILE",/unique)
for i=0, n_elements(files)-1 do begin
fits_file = self->get_full_file_name(files[i])
fits_obj= self->get_new_fits_obj(fits_file)
self->update_flags_with_fits_file, fits_obj
obj_destroy, fits_obj
endfor
END
;+
; Creates a new index file using current info (file_path, observer, etc.) for index header.
;
; @private
;-
PRO IO_SDFITS::init_index
compile_opt idl2
self.index->set_file_name, self.index->get_file_name()
self.index->new_file, self.observer, self.backend, self.tcal_table, self.file_path
END
;+
; This method will read an index file, check that the index agrees with
; the sdfits files on disk, and create fits objects for the files listed
; in its index.
;
; @param file_name {in}{type=string} Index file name (no path)
;
; @keyword file_path {in}{optinal}{type=string} Where to find index file and sdfits files
;
; @examples
;
; path = '/users/me/my_project'
; io = obj_new('io_sdfits_line')
; io->load_index, 'my_index', file_path='/users/me/my_project'
;
;-
PRO IO_SDFITS::load_index, file_name, file_path=file_path
compile_opt idl2
if keyword_set(file_path) then self->set_file_path, file_path
; load the index info
self.index->read_file, file_name=file_name, ver_status
; if the version number is old, then we can't even read it
if ver_status eq 0 then begin
message, "Cannot load pre-existing index: old version number."
return
endif
; if the index info makes no sense, dont use it
if (self.index->check_index_with_reality() eq 0) then begin
message, 'Cannot load pre-existing index: does not match files'
return
endif
; create fits objs for files listed in index, if need be
self->conform_fits_objs_to_index
; we're good to go
self.index_synced = 1
END
;+
; Calls index file's read_file method
; @param file_name {in}{type=string} full path name to the index file
; @uses INDEX_FILE::read_file
; @private
;-
PRO IO_SDFITS::read_index, file_name
compile_opt idl2
self.index->read_file, file_name=file_name
END
;+
; Creates a fits object for the file name passed to it, and adds it to the list of fits objects
; @param file_name {in}{type=string} full path name of the sdfits file to add
; @uses IO_SDFITS::get_full_file_name
; @uses IO_SDFITS::get_new_fits_obj
; @private
;-
PRO IO_SDFITS::add_fits_obj, file_name, result, _EXTRA=ex
result = 0
; get the full path
full_file_name = self->get_full_file_name(file_name)
; check to see if this fits object already exists
if (self->find_fits(full_file_name) ne -1) then begin
message, "Cannot add fits file, fits object exists already for fits file: "+full_file_name, /info
return
endif
new_fits = self->get_new_fits_obj(full_file_name, _EXTRA=ex)
; pass on the debug state
if self.debug then new_fits->set_debug_on
if (obj_valid(new_fits) eq 0) then begin
message, "Could not create valid sdfits object for file: "+full_file_name,/info
return
endif
if ptr_valid(self.fits_files) then begin
*self.fits_files = [*self.fits_files, new_fits]
endif else begin
self.fits_files = ptr_new([new_fits])
endelse
; if we added the fits object properly, add this info to the flags object
self.flags->check_flag_file_for_fits_file, file_name
result = 1
END
;+
; From an array of full path names to sdfits files, create an array of sdfits objects
; @private
;-
PRO IO_SDFITS::add_fits_objs, file_names
compile_opt idl2
for i=0,n_elements(file_names)-1 do begin
self->add_fits_obj, file_names[i]
endfor
END
;+
; Creates a new index file, populating it with information from all
; the fits object
; @param index_file_name {in}{type=string} full path name to index file
; @uses SDFITS::get_rows
; @uses FITS::get_extension_header_value
; @uses IO_SDFITS::init_index
; @uses IO_SDFITS::update_index_with_fits_file
; @private
;-
PRO IO_SDFITS::create_index_file, index_file_name
compile_opt idl2
print, "About to create Index..."
; need to keep flags object in sync with fits files in index file
; so, reset this object
if obj_valid(self.flags) then obj_destroy, self.flags
self.flags = obj_new("flags",file_path=self.file_path,debug=self.debug)
fits_files = *self.fits_files
; HACK HACK HACK
; use the first fits object to init index with common info
fits_obj = fits_files[0]
;rows = fits_obj->get_rows(ext=ext,row_nums=[0])
;self.observer = rows[0].observer
self.observer = 'unknown'
self.backend = fits_obj->get_extension_header_value('BACKEND')
self.tcal_table = 'unknown'
self->init_index
; use fits objects to populate index file
for i = 0, (n_elements(fits_files)-1) do begin
fits_obj = fits_files[i]
; update the index file
self->update_index_with_fits_file, fits_obj
endfor
self.index_synced = 1
print, "Index file created."
END
;+
; Creates a new index files for an entire project,
; this includes the master index file for the directory
; and all the auxillary index files, one per fits file.
; This is a variation of create_index_file.
; @param file_paths {in}{type=array}{required} array of full path names to fits files
; @private
;-
PRO IO_SDFITS::set_project_create_index_file, file_paths
compile_opt idl2
print, "About to create Index..."
; create new fits objects for all files in directory
self->free_fits_objs
for i = 0,n_elements(file_paths)-1 do begin
self->add_fits_obj, file_paths[i], result
if not result then begin
message, "Cannot add file: "+file_paths[i],/info
return
endif
endfor
; need to keep flags object in sync with fits files in index file
; so, reset this object
if obj_valid(self.flags) then obj_destroy, self.flags
self.flags = obj_new("flags",file_path=self.file_path,debug=self.debug)
fits_files = *self.fits_files
; HACK HACK HACK
; use the first fits object to init index with common info
fits_obj = fits_files[0]
;rows = fits_obj->get_rows(ext=ext,row_nums=[0])
;self.observer = rows[0].observer
self.observer = 'unknown'
self.backend = fits_obj->get_extension_header_value('BACKEND')
self.tcal_table = 'unknown'
self->init_index
indexNames = strarr(n_elements(fits_files))
; use fits objects to populate index file
for i = 0, n_elements(fits_files)-1 do begin
; create an auxillary index for each fits file
status = 1
fits_obj = fits_files[i]
index_name = self->get_expected_full_index_name(fits_obj)
indexNames[i] = index_name
self->create_index_for_fits_obj, index_name, fits_obj, status
if status eq 0 then begin
message, "failed to created index: " + index_name
endif
endfor
; sort the index names - makes the result less surprising
indexNames = indexNames[sort(indexNames)]
indxIters = objarr(n_elements(indexNames))
nextIds = strarr(n_elements(indexNames))
for i = 0, n_elements(indexNames)-1 do begin
indxIters[i] = obj_new('index_iterator',indexNames[i],self->get_index_section_class_name())
nextIds[i] = (indxIters[i])->next_id()
endfor
; start with the minimum (should be
; earliest) nextId, excluding empty
; strings
while 1 do begin
idsOK = where(strlen(nextIds) gt 0, count)
if count eq 0 then break
; there are IDs that are OK to use - keep iterating
minID = min(nextids[idsOK])
minLoc = where(nextIds eq minID,count)
for i=0,(count-1) do begin
thisIter = indxIters[minLoc[i]]
rows = thisIter->next()
nextIds[minLoc[i]] = thisIter->next_id()
if self.index->update_file_with_row_structs(rows) eq 0 then begin
message, "failed attempt to use fits index to update index: "+thsiIter->get_name()
endif
if strlen(thisIter->next_id()) eq 0 then begin
; can destroy this iterator at this
; point - no longer needed
obj_destroy, thisIter
endif
endfor
endwhile
; no clean up necessary, the iterators
; must all be destroyed to get here
self.index->read_file
self.index_synced = 1
; must wait to update flag files here
; - needs index to be there for this
; flag file as its updated
for i = 0, n_elements(fits_files)-1 do begin
; update the flag files
self->update_flags_with_fits_file, fits_files[i]
endfor
print, "Index file created."
END
;+
; Every sdfits file that is read in must have an index file. Even if multiple
; sdfits files are managed by a single index file, this master index file gets
; its info from the individual index files.
; This method creates an index file for the given fits object.
;-
PRO IO_SDFITS::create_index_for_fits_obj, index_file_name, fits_obj, status
compile_opt idl2
status = 0
fits_name = fits_obj->get_file_name()
print, "About to create Auxillary Index for fits file: "+fits_name+" ..."
; init index
index_name = self->get_index_name_from_fits_name(fits_name)
auxillary_index = obj_new(self->get_index_class_name(),file_name=index_name,version=self.version)
auxillary_index->set_file_path, self.file_path
observer = 'unknown'
; "BACKEND" is a keyword in SDFITS, but a column in SDFITS from gbtidl.
backend = fits_obj->get_extension_header_value('BACKEND')
if size(backend, /type) eq 2 then backend = "unknown"
tcal_table = 'unknown'
auxillary_index->set_file_name, auxillary_index->get_file_name()
auxillary_index->new_file, observer, backend, tcal_table, self.file_path
; use fits objects to populate index file
self->update_index_obj_with_fits_obj, auxillary_index, fits_obj
; cleanup, since all we need to do is create the file
if obj_valid(auxillary_index) then obj_destroy, auxillary_index
print, "Auxillary Index file created."
status = 1
END
;+
; For every sdfits file managed by the current index file, the flags object
; must be able to convert the record numbers in the current index file to
; record numbers stored in each flag file.
; @param fits_obj {in}{required}{type=object} object representing an sdfits file referred to in the current index.
;-
PRO IO_SDFITS::update_flags_with_fits_file, fits_obj
compile_opt idl2, hidden
; get the name of the fits file
fits_name = fits_obj->get_file_name()
; get the first index number for this fits file
base_index = self.index->get_base_index_for_file(fits_name)
self.flags->set_file_path, self.file_path
; update the flags object to load this flag file (if found) and its info
self.flags->update_flags, fits_name, base_index
END
;+
; Checks to make sure index file that is loaded agrees with this object,
; and will create new sdfits objects if necessary to conform.
; @uses INDEX_FILE::get_column_values
; @uses INDEX_FILE::get_header_value
; @uses IO_SDFITS::file_exists
; @uses INDEX_FILE::search_for_row_info
; @uses IO_SDFITS::group_row_info
; @uses FITS_OPEN
; @uses FITS_CLOSE
; @uses IO_SDFITS::free_group_row_info
; @uses IO_SDFITS::conform_fits_objs
; @uses IO_SDFITS::set_file_path
; @private
;-
FUNCTION IO_SDFITS::conform_to_index
compile_opt idl2
; check that files in index all exist
files = self.index->get_column_values("file",/unique)
file_path = self.index->get_header_value("file_path")
for i = 0, n_elements(files)-1 do begin
if (file_path ne '') then begin
file_name = file_path + '/' + files[i]
endif else begin
file_name = files[i]
endelse
if (self->file_exists(file_name) eq 0) then begin
message, 'Cannot confirm index: file does not exist: '+file_name
return, 0
endif
if (i eq 0) then file_names=[file_name] else file_names=[file_names,file_name]
endfor
; check the extensions, get all rows
row_infos = self.index->search_for_row_info()
; group all rows in index file by filename and extension
row_groups = self->group_row_info(row_infos)
; check the extensions
for i = 0, n_elements(row_groups)-1 do begin
if (file_path ne '') then begin
file_name = file_path + '/' + row_groups[i].file
endif else begin
file_name = row_groups[i].file
endelse
FITS_OPEN, file_name,fcb,/no_abort, message=msg
if strlen(msg) ne 0 then begin
message,'FITS_OPEN failed in FITS::conform_to_index',/info
message,msg,/info
message,'problem file : '+ file_name,/info
fileDir = file_dirname(file_name)
if file_test(fileDir,/directory,/write) then begin
badDir = fileDir + '/badfits'
if not file_test(badDir,/write) then begin
file_mkdir,badDir
endif
if file_test(badDir,/directory,/write) then begin
thisfileBase = file_basename(file_name)
copyFile = badDir + '/' + thisfileBase
if file_test(copyFile) then begin
copyFile = copyFile + strtrim(string(round(systime(/sec)-1.129e9)),2)
endif
if not file_test(copyFile) then begin
file_copy,file_name,copyFile,/overwrite
message,'Copied to '+copyFile,/info
endif else begin
message,'Could not copy file, file already exists',/info
endelse
endif else begin
message,'Could not copy file, no write permission in '+badDir,/info
endelse
endif
retall
endif
num_extensions = fcb.nextend
FITS_CLOSE,fcb
if (row_groups[i].extension gt num_extensions) then begin
message, 'Cannot confirm: file does not contain extension: '+string(row_groups[i].extension)
return, 0
endif
endfor
self->free_group_row_info, row_groups
; check that we have the fits objects for these files
self->conform_fits_objs, file_names
; we can set our member variables according to the index file
self->set_file_path, file_path
; return success
return, 1
END
;+
; Called after loading an index, gets all files in index and calls conform_fits_objs
; @uses IO_SDFITS::get_index_files
; @uses IO_SDFITS::conform_fits_objs
; @private
;-
PRO IO_SDFITS::conform_fits_objs_to_index
file_names = self->get_index_files()
if (self.file_path ne '') then begin
file_names = self.file_path +'/'+file_names
endif
self->conform_fits_objs, file_names
END
;+
; Checks that the list of file names passed to it are all represented by fits objects.
; If not, the fits objects are created from scratch.
; @param file_names {in}{type=array} array of full path names to sdfits files
; @uses IO_SDFITS::get_fits
; @uses IO_SDFITS::add_fits_objs
; @private
;-
PRO IO_SDFITS::conform_fits_objs, file_names
compile_opt idl2
missing_fits = 0
for i = 0, n_elements(file_names)-1 do begin
fits = self->get_fits(file_names[i])
if (obj_valid(fits) eq 0) then missing_fits = 1
endfor
; if we are missing a fits object, we can recover from this
if (missing_fits eq 1) then begin
; recreate ALL fits objects
if self.debug then print, "creating new fits objects for all files:"
if self.debug then print, file_names
self->free_fits_objs
self->add_fits_objs, file_names
endif
return
END
;+
; Takes in a file name string and returns the full path, with prepended path if needed.
; If the object has no file path, then the file name is returned unaltered. If
; the object DOES have a file path, then the passed in file name is checked for backslashes.
; If there is no backslash, the objects file path is prepended to the file name and passed back.
; @param file_name {in}{type=string} a file name, can be full path or not.
; @returns Either the original passed in file name, or the full path file name.
; @private
;-
FUNCTION IO_SDFITS::get_full_file_name, file_name
compile_opt idl2
; if there is no file path, do nothing
if (self.file_path eq '') then begin
full_file_name = file_name
endif else begin
; if there is a file path, see if the file_name already has one
if (strpos(file_name,'/') eq -1) then begin
; we must add the file path
full_file_name = self.file_path + '/' + file_name
endif else begin
; the name already has some kind of path in it
full_file_name = file_name
endelse
endelse
return, full_file_name
END
;+
; Sets the path where index file and all sdfits files are to be found
;
; @param file_path {in}{type=string} Path where index file and all sdfits files are to be found.
;
; @examples
;
; path = '/users/me/my_project'
; io = obj_new('io_sdfits_line')
; io->set_file_path, path
;
;-
PRO IO_SDFITS::set_file_path, file_path
compile_opt idl2
self.file_path = file_path
self.index->set_file_path, file_path
self.flags->set_file_path, file_path
END
;+
; Gets the path where index file and all sdfits files are to be found
;
; @returns {type=string} Path where index file and all sdfits files are to be found.
;
; @examples
;
; path = '/users/me/my_project'
; io = obj_new('io_sdfits_line')
; io->set_project, path
; print, io->get_file_path()
; '/users/me/my_project'
;
;-
FUNCTION IO_SDFITS::get_file_path
compile_opt idl2
return, self.file_path
END
;+
; Checks to see if this object has any sdfits files connected to it.
; @returns 0 - data is not loaded; 1 - data is loaded.
;-
FUNCTION IO_SDFITS::is_data_loaded
compile_opt idl2
return, self.index_synced
END
;+
; Sets the file name of the index file.
;-
PRO IO_SDFITS::set_index_file_name, file_name
compile_opt idl2
self.index->set_file_name, file_name
END
;+
; Retrieves the file name of the index file.
; @keyword full {in}{optional}{type=boolean} wether to return the full path name of the index file or not
; @returns The file name of the index file
;-
FUNCTION IO_SDFITS::get_index_file_name, full=full
compile_opt idl2
if keyword_set(full) then begin
file_name = self.index->get_full_file_name()
endif else begin
file_name = self.index->get_file_name()
endelse
return, file_name
END
;+
; Given the full path name of an sdfits file, returns the object
; that represents that file
; @param file_name {in}{type=string} full path name to an sdfits file
; @uses IO_SDFITS::get_full_file_name
; @returns The index for the sdfits object to represent that represents this file, or -1 if object not found
; @private
;-
FUNCTION IO_SDFITS::find_fits, file_name
compile_opt idl2
full_file_name = self->get_full_file_name(file_name)
if self.debug then print, "searching for: "+full_file_name +" in list: "
if (ptr_valid(self.fits_files) eq 0) then return, -1
fits_files = *self.fits_files
fits_found = 0
i = 0
while (fits_found eq 0) and (i lt n_elements(*self.fits_files)) do begin
fits_obj = fits_files[i]
if self.debug then print, "fits obj #"+string(i)+" "+fits_obj->get_full_file_name()
if (full_file_name eq fits_obj->get_full_file_name()) then fits_found = 1 else i = i + 1
endwhile
if self.debug then print, "fits found: "+string(fits_found)
if (fits_found eq 1) then return, i else return, -1
END
;+
; @returns an array of the file names of all of the FITS files
; associated with this io_sdfits object. Returns -1 if none or not
; initialized.
; @private
;-
FUNCTION IO_SDFITS::get_fits_file_names
compile_opt idl2
if (ptr_valid(self.fits_files) eq 0) then begin
return, -1
endif
result = strarr(n_elements(*self.fits_files))
for i=0,(n_elements(result)-1) do begin
result[i] = (*self.fits_files)[i]->get_file_name()
endfor
return,result
end
;+
; @param file_name {in}{type=string} full path name to an sdfits file
; @uses IO_SDFITS::find_fits
; @returns The the sdfits object to represent that represents this file, or -1 if object not found
; @private
;-
FUNCTION IO_SDFITS::get_fits, file_name
compile_opt idl2
index = self->find_fits(file_name)
if (index eq -1) then begin
return, -1
endif else begin
fits_files = *self.fits_files
return, fits_files[index]
endelse
END
;+
; Retrieves the number of extensions for the given sdfits file
; @param file_name {in}{type=string} full file name of the sdfits file in question
; @returns number of extensions for file_name
; @private
;-
FUNCTION IO_SDFITS::get_number_extensions, file_name
compile_opt idl2
fits = self->get_fits(file_name)
if obj_valid(fits) then return, fits->get_number_extensions() else return, -1
END
;+
; Retrieves the extension type for the given sdfits file and extension
; @param ext_num {in}{type=long} extension (1-based)
; @param file_name {in}{type=string} full file name of the sdfits file in question
; @returns extension type for file_name and extension
; @private
;-
FUNCTION IO_SDFITS::get_extension_type, ext_num, file_name
compile_opt idl2
fits = self->get_fits(file_name)
if obj_valid(fits) then return, fits->get_extension_type(ext_num) else return, -1
END
;+
; Retrieves the extension name for the given sdfits file and extension
; @param ext_num {in}{type=long} extension (1-based)
; @param file_name {in}{type=string} full file name of the sdfits file in question
; @returns extension name for file_name and extension number
; @private
;-
FUNCTION IO_SDFITS::get_extension_name, ext_num, file_name
compile_opt idl2
fits = self->get_fits(file_name)
if obj_valid(fits) then return, fits->get_extension_name(ext_num) else return, -1
END
;+
; Retrieves the number of rows for the given sdfits file and extension
; @param ext_num {in}{type=long} extension (1-based)
; @param file_name {in}{type=string} full file name of the sdfits file in question
; @returns number of rows for file_name and extension
; @private
;-
FUNCTION IO_SDFITS::get_ext_num_rows, ext_num, file_name
compile_opt idl2
fits = self->get_fits(file_name)
if obj_valid(fits) then return, fits->get_ext_num_rows(ext_num) else return, -1
END
;+
; Prints out rows from the index file used by object. For exact search parameters
; to enter, see LINE_INDEX::search_index or
; CNTM_INDEX::search_index
;
; @param start {in}{optional}{type=long} where to start the range to list
; @param finish {in}{optional}{type=long} where to stop the range to list
; @keyword sortcol {in}{optional}{type=string} what index column name to order listwith
; @keyword verbose {in}{optinal}{type=boolean}{default=0} Print out ALL information?
; @keyword user {in}{optional}{type=boolean} print out columns specified using set_user_columns? Takes precedence over verbose keyword
; @keyword columns {in}{optional}{type=string array} array of column
; names to print out upon list command. Takes precedence over user and
; verbose keywords.
; @keyword file {in}{optional}{type=string}{default=/dev/tty} The file
; to write to. Defaults to the current screen, using "more" to page
; the output.
;
;-
PRO IO_SDFITS::list, start, finish, sortcol=sortcol,verbose=verbose, user=user,$
columns=columns,file=file,_EXTRA=ex
compile_opt idl2
; validate search arguments
if self.index->validate_search_keywords(ex) eq 0 then begin
message, "Error with search keywords, cannot perform list", /info
return
endif
if self->check_search_param_syntax(_EXTRA=ex) eq 0 then return
; validate columns to be listed
if n_elements(columns) ne 0 then begin
if self.index->validate_column_names(columns) eq 0 then begin
message, "Error with column names, cannot perform list", /info
return
endif
endif
; if we're online, read in the latest index rows into memory
if self.online then self->update
; find the rows in the index file that meat our search criteria
results = self.index->search_index(start, finish, _EXTRA=ex)
if (size(results,/dim) eq 0) then begin
if results eq -1 then return
endif
; order the search results
if n_elements(sortcol) ne 0 then begin
results = self->sort_search_results(results,sortcol)
endif
; print out the requested information
self.index->list, rows=results,verbose=verbose,columns=columns,user=user, file=file,_EXTRA=ex
END
;+
; Returns indicies of rows in index file that match search. For exact search parameters
; to enter, see LINE_INDEX::search_index or
; CNTM_INDEX::search_index
; @uses INDEX_FILE::search_index
; @returns Long array of indicies of rows in index file that match search
;-
FUNCTION IO_SDFITS::get_index, _EXTRA=ex
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
if self.index->validate_search_keywords(ex) eq 0 then begin
message, "Error with search keywords, cannot perform search", /info
return, -1
endif
ind = self.index->search_index(_EXTRA=ex)
if (size(ind,/dim) eq 0) then begin
if (ind lt 0) then return, -1
endif
return, ind
END
;+
; Returns an array of structures that contains info about the scan number given, such
; as scan number, procedure name, number of integrations, ifs, etc..
; A separate element is returned for each unique TIMESTAMP and file
; for all rows having that scan number.
; @param scan_number {in}{type=long} scan number information is
; queried for
; @param file {in}{optional}{type=string} Limit the search for the
; scan number to a specific file name.
; @keyword count {out}{type=integer} The number of elements of the
; returned array of structures.
; @keyword quiet {in}{optional}{type=boolean} When set, suppress most
; error messages.
; @uses INDEX_FILE::get_scan_info
; @returns Array of structure containing info on scan
;-
FUNCTION IO_SDFITS::get_scan_info, scan_number, file, count=count, quiet=quiet
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
scan_info = self.index->get_scan_info(scan_number,file,count=count,quiet=quiet)
return, scan_info
END
;+
; Prints out the header section of the index file used by this object
; @uses INDEX_FILE::list_header
;-
PRO IO_SDFITS::list_index_header
compile_opt idl2
self.index->list_header
END
;+
; Returns the values contained in the index file column used by this object
; @param column_name {in}{required}{type=string} name of the column to query
; @uses INDEX_FILE::get_column_values
; @returns the values found in the index column name specified.
;-
FUNCTION IO_SDFITS::get_index_values, column_name, _EXTRA=ex
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
if self.index->validate_search_keywords(ex) eq 0 then begin
message, "Error with search keywords, cannot perform search", /info
return, -1
endif
return, self.index->get_column_values(column_name,_EXTRA=ex)
END
;+
; Returns the unique file names (no path) contained in the index file used by this object
; @uses INDEX_FILE::get_column_values
; @returns The unique file names (no path) contained in the index file used by this object
;-
FUNCTION IO_SDFITS::get_index_files, _EXTRA=ex, full=full
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
if keyword_set(full) then begin
f = self.index->get_column_values("file",/unique)
path = self->get_file_path()
files = strarr(n_elements(f))
for i =0, n_elements(f) - 1 do begin
files[i] = path + '/' + f[i]
endfor
endif else begin
files = self.index->get_column_values("file",/unique)
endelse
return, files
END
;+
; Returns the unique project names (no path) contained in the index file used by this object
; @uses INDEX_FILE::get_column_values
; @returns The unique project names (no path) contained in the index file used by this object
;-
FUNCTION IO_SDFITS::get_index_projects, _EXTRA=ex
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
return, self.index->get_column_values("project",/unique)
END
;+
; Returns the unique source names (no path) contained in the index file used by this object
; @uses INDEX_FILE::get_column_values
; @returns The unique source names (no path) contained in the index file used by this object
;-
FUNCTION IO_SDFITS::get_index_sources, _EXTRA=ex
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
return, self.index->get_column_values("source",/unique)
END
;+
; Returns the unique procedure names (no path) contained in the index file used by this object
; @uses INDEX_FILE::get_column_values
; @returns The unique procedure names (no path) contained in the index file used by this object
;-
FUNCTION IO_SDFITS::get_index_procedures, _EXTRA=ex
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
return, self.index->get_column_values("procedure",/unique)
END
;+
; Returns the unique scan names (no path) contained in the index file used by this object
; @uses INDEX_FILE::get_column_values
; @returns The unique scan names (no path) contained in the index file used by this object
;-
FUNCTION IO_SDFITS::get_index_scans, _EXTRA=ex
compile_opt idl2
; if we're online, read in the latest index rows into memory
if self.online then self->update
return, self.index->get_column_values("SCAN",/unique, _EXTRA=ex)
END
;+
; Groups rows from the index table and sorts them by file-extension, since if we read these rows
; we want to do that efficiently (one file-extension at a time).
; @param row_info {in}{type=array} array of structures that mirror rows in the index file
; @returns an array of structures, each struct representing index file rows grouped by file-extension.
; @private
;-
FUNCTION IO_SDFITS::group_row_info, row_info
compile_opt idl2
; get all files
files = row_info.file
unique_files = files[uniq(files[sort(files)])]
group = {sdfits_row_group}
for i = 0, (n_elements(unique_files)-1) do begin
file_locals = row_info[ where(row_info.file eq unique_files[i]) ]
exts = file_locals.extension
unique_exts = exts[uniq(exts[sort(exts)])]
for j = 0, (n_elements(unique_exts)-1) do begin
file_ext_locals = file_locals[ where(file_locals.extension eq unique_exts[j]) ]
; collapse the array into one struct
group.file = file_ext_locals[0].file
group.extension = file_ext_locals[0].extension
group.rows = ptr_new(file_ext_locals.row_num)
group.integrations = ptr_new(file_ext_locals.integration)
group.if_numbers = ptr_new(file_ext_locals.if_number)
if (i eq 0) and (j eq 0) then groups = [group] else groups = [groups,group]
endfor
endfor
return, groups
END
;+
; Deprecated function: given a listing of rows from the same file-extension, this uses
; an sdfits objects to simply return those rows from the file.
; @param row_group {in}{type=struct} struct that specifies location of rows to be read from a file-extension
; @uses IO_SDFITS::get_fits
; @uses SDFITS::get_sdfits_rows
; @returns array of structs mirroring the rows in the sdfits file specified to read in.
; @private
;-
FUNCTION IO_SDFITS::get_sdfits_rows, row_group
compile_opt idl2
rows_ptr = row_group.rows
ext = row_group.extension
rows = *rows_ptr
fits = self->get_fits(row_group.file)
if obj_valid(fits) then begin
if self.debug then print, fits->get_file_name()
if self.debug then print, fits->get_full_file_name()
sdfits_rows = fits->get_sdfits_rows(ext=ext, row_nums=rows)
endif else begin
message, 'no fits object for: '+row_group.file
return, -1
endelse
return, sdfits_rows
END
;+
; Given a listing of rows from the same file-extension, this uses
; an sdfits object to simply return those rows from the file and evaluate what
; columns are missing and what are the keywords for the extension
; @param row_group {in}{type=struct} struct that specifies location of rows to be read from a file-extension
; @param missing {out}{type=array} array of column names that were expected in extension, but are missing
; @param virtuals {out}{type=struct} struct that contains keywords
; that were found in the extension header
; @param apply_offsets {out}{type=boolean} If 1 (true) then any
; non-zero beam offsets should be applied and the positions adjusted
; accordingly.
; @uses IO_SDFITS::get_fits
; @uses SDFITS::get_and_eval_rows
; @returns array of structs mirroring the rows in the sdfits file specified to read in.
; @private
;-
FUNCTION IO_SDFITS::get_and_eval_rows, row_group, missing, virtuals, apply_offsets
rows_ptr = row_group.rows
ext = row_group.extension
rows = *rows_ptr
fits = self->get_fits(row_group.file)
if obj_valid(fits) then begin
sdfits_rows = fits->get_and_eval_rows(missing, virtuals, rows, ext=ext)
apply_offsets = fits->auto_apply_offsets()
endif else begin
message, 'no fits object for: '+row_group.file
return, -1
endelse
return, sdfits_rows
END
;+
; Method for attempting to extract a value from an sdfits row. If the row contains the
; tag name requested, that value is passed back. If that tag name actually specifies a
; keyword in the extension-header, and NOT a column, then that value is returned. Finally,
; if the tag name matches one of the expected column names that were not found in this
; extension, the default value is returned.
; @param row {in}{type=struct} structure that mirrors a row in an sdfits file
; @param tag_name {in}{type=string} name of the value that we want to retrieve
; @param virtuals {in}{type=struct} struct giving the keyword-values found in the file-extension
; @param names {in}{type=struct} struct contiaining pointers to the names of columns in the row, missing columns, and tag names in the virtuals struct
; @param default_value {in} value to be returned if the tag_name is of a missing column
; @returns either the value of row.tag_name, virtauls.tag_name, or default_value
; @private
;-
FUNCTION IO_SDFITS::get_row_value, row, tag_name, virtuals, names, default_value
compile_opt idl2
; look for the tag name inside each member of 'names'
i = where(tag_name eq *names.row)
if (i ne -1) then begin
; its in the sdfits row
value = row.(i)
endif else begin
; see if there are virtual cols to check
if (size(*names.virtuals,/dim) ne 0) then begin
i = where(tag_name eq *names.virtuals)
endif else begin
i = -1
endelse
if (i ne -1) then begin
; its a keyword in ext header (virtual)
value = virtuals.(i)
endif else begin
; see if there are missing cols to check
if (size(*names.missing,/dim) ne 0) then begin
i = where(tag_name eq *names.missing)
endif else begin
i = -1
endelse
if (i ne -1) then begin
; its a missing column from sdfits row
value = default_value ;missing.(i)
endif else begin
; use the default value again
;print, 'tag_name: '+tag_name+' not found in row, missing, or virtuals'
value = default_value
endelse
endelse
endelse
; strip off trailing whitespace
if size(value,/type) eq 7 then value = strtrim(value)
return, value
END
;+
; Translates the polarization from the sdfits integer value into a char based value
; @returns Char representation of polarization
; @private
;-
FUNCTION IO_SDFITS::format_sdfits_polarization, pol
compile_opt idl2
case pol of
1: value = 'I'
2: value = 'Q'
3: value = 'U'
4: value = 'V'
-1: value = 'RR'
-2: value = 'LL'
-3: value = 'RL'
-4: value = 'LR'
-5: value = 'XX'
-6: value = 'YY'
-7: value = 'XY'
-8: value = 'YX'
else: value = '?'
endcase
return, value
END
;+
; Translates the sdfits string for frequency type to the gbtidl representation
;
; CTYPE1 has two parts:
;
; First 4 characters are the type of quantity on that axis. Possible
; values are:
;
; FREQ == frequency. The axis is linear in frequency.
; VELO == velocity. The axis is linear in velocity.
; FELO == The axis is linear in frequency, but the quantities describing
; it are given in velocity units in the optical convention.
;
; These all date from a AIPS memo by Eric Griesen from November of 1983.
; The WCS paper III finally gets it right and eventually we'll have to
; update SDFITS to do that as Arecibo apparently has done. SDFITS,
; though, is still stuck in 1983.
;
; The second 4 characters describe the reference frame. These come
; from M&C, which uses these codes:
; OBS - observed (sky frequencies)
; GEO - geocentric
; BAR - barycentric
; HEL - heliocentric
; GAL - galactic
; LSR - Local Standard of Rest (equivalent to Kinematic LSR or LSRK)
; LSD - Dynamic Local Standard of Rest
; LGR - Local group
; CMB - Cosmic Microwave Background
;
; There's a "-" as the first character in this second set of 4.
;
; For reasons I can't remember, we decided that OBS should be translated
; to TOPO
; but everything else was okay as is.
;
; The sdfits tool at the moment always sets CTYPE1=FREQ-OBS, but of course
; other
; SDFITS files might have other combinations.
;
; For the moment, ensure that the first 4 characters are FREQ, and set
; frequency_type
; to the characters after the "-", translating "OBS" to "TOPO".
;
; Eventually, we'll want to translate VELO and FELO to true FREQ axes in the
; data container, but I think you should just reject those for now and
; come back to it
; later.
; @private
;-
FUNCTION IO_SDFITS::format_sdfits_freq_type, freq_type
compile_opt idl2
freq_type = strtrim(freq_type,2)
first_half = strmid(freq_type,0,4)
; first part must be FREQ or its an error
if (first_half eq 'FREQ' ) then begin
; ignore any dashes in the second half
first_char = strmid(freq_type,4,1)
if (first_char eq '-') then begin
second_half = strmid(freq_type,5,3)
endif else begin
second_half = strmid(freq_type,4,4)
endelse
; translate some second half values
if (second_half eq 'OBS') then second_half = 'TOPO'
value = second_half
endif else begin
value = 'unknown'
endelse
return, value
END
;+
; Deprecated
; @private
;-
FUNCTION IO_SDFITS::format_sdfits_procedure, obsmode
compile_opt idl2
obsmodes = strsplit(obsmode,":",/extract)
return, obsmodes[0]
END
;+
; Translates the sdfits value for obsmode into seperate procedure, switch state and switch signal values
; @param obsmode {in}{type=string} the obsmode column from sdfits; takes form 'proc:swstate:swtchsig'
; @param proc {out}{type=string} procedure
; @param swstate {out}{type=string} switch state
; @param swtchsig {out}{type=string} switch signal
; @private
;-
PRO IO_SDFITS::parse_sdfits_obsmode, obsmode, proc, swstate, swtchsig
parts = strsplit(strtrim(obsmode),":",/extract)
if (n_elements(parts) ne 3) then begin
if (n_elements(parts) eq 1) then begin
; only one part, give it to proc
proc = parts
endif else begin
; I have no clude, give all of obsmode to proc as is
proc = strtrim(obsmode) ; give all of obsmode to proc
endelse
; either way, these are unknown
swstate = 'unknown'
swtchsig = 'unknown'
endif else begin
proc = parts[0]
swstate = parts[1]
swtchsig = parts[2]
endelse
END
;+
; Incomplete: Translates the sdfits values for longitutde and latitude type into a coordinate mode.
; @param long_type {in}{type=string} type of longitude coordinate
; @param lat_type {in}{type=string} type of latitude coordinate
; @returns coordinate mode
; @private
;-
FUNCTION IO_SDFITS::coord_mode_from_types, long_type, lat_type
compile_opt idl2
; default value
value = 'OTHER'
long_type = strtrim(strupcase(long_type),2)
lat_type = strtrim(strupcase(lat_type),2)
if (long_type eq 'RA') and (lat_type eq 'DEC') then begin
value = 'RADEC'
endif
if (long_type eq 'GLON') and (lat_type eq 'GLAT') then begin
value = 'GALACTIC'
endif
if (long_type eq 'HA') and (lat_type eq 'DEC') then begin
value = 'HADEC'
endif
if (long_type eq 'AZ') and (lat_type eq 'EL') then begin
value = 'AZEL'
endif
if (long_type eq 'OLON') and (lat_type eq 'OLAT') then begin
value = 'OTHER'
endif
return, value
END
;+
; Translates sdfits cal string value into 1 or 0
; @param cal {in}{type=string} sdfits sig value
; @returns 1,0
; @private
;-
FUNCTION IO_SDFITS::translate_cal, cal
compile_opt idl2
; cal default is 0
if (cal eq 'T') then return, 1 else return, 0
END
;+
; Translates sdfits sig string value into 1 or 0
; @param sig {in}{type=string} sdfits sig value
; @returns 1,0
; @private
;-
FUNCTION IO_SDFITS::translate_sig, sig
compile_opt idl2
; sig default is 1
if (sig eq 'T') or (sig eq 'U') then return, 1 else return, 0
END
;+
; Releases the memory for all the current sdfits objects for this object
; @private
;-
PRO IO_SDFITS::free_fits_objs
compile_opt idl2
if (ptr_valid(self.fits_files)) then begin
fits_files = *self.fits_files
for i = 0,(n_elements(fits_files)-1) do begin
if obj_valid(fits_files[i]) then obj_destroy,fits_files[i]
endfor
ptr_free,self.fits_files
endif
END
;+
; Sets debug flag for all fits objs
; @private
;-
PRO IO_SDFITS::set_debug_for_fits_objs, debug
compile_opt idl2
if (ptr_valid(self.fits_files)) then begin
fits_files = *self.fits_files
for i = 0,(n_elements(fits_files)-1) do begin
if debug then begin
if obj_valid(fits_files[i]) then fits_files[i]->set_debug_on
endif else begin
if obj_valid(fits_files[i]) then fits_files[i]->set_debug_off
endelse
endfor
ptr_free,self.fits_files
endif
END
;+
; Diagnostic function to determine if index file indicies are unique (as they should be)
; @returns 0 - bad, 1 - good
;-
FUNCTION IO_SDFITS::are_index_file_indicies_unique
compile_opt idl2
return, self.index->are_index_file_indicies_unique()
END
;+
; Sets the object to print rows using the interactive 'more' format
;-
PRO IO_SDFITS::set_more_format_on
compile_opt idl2, hidden
self.index->set_more_format_on
END
;+
; Sets the object NOT to print rows using the interactive 'more' format
;-
PRO IO_SDFITS::set_more_format_off
compile_opt idl2, hidden
self.index->set_more_format_off
END
;+
; Prints the available columns from the rows section for list;
; these are also the valid search keywords
;-
PRO IO_SDFITS::list_available_columns
compile_opt idl2, hidden
self.index->list_available_columns
END
;+
; Sets what columns should be used for user listing
; @param columns {in}{required}{type=string array} array of columns to print on list command
;-
PRO IO_SDFITS::set_user_columns, columns
compile_opt idl2, hidden
self.index->set_user_columns, columns
END
;+
; Prints the columns currently selected for the user specified listing
;-
PRO IO_SDFITS::list_user_columns
compile_opt idl2, hidden
self.index->list_user_columns
END
;+
; Retrieves number of records the object is connected to - or how many rows
; currently in the index file
; @returns number of records this object is connected to
;-
FUNCTION IO_SDFITS::get_num_index_rows
compile_opt idl2, hidden
if self.online then self->update
return, self.index->get_num_index_rows()
END
;+
;
; Given an integer array representing some rows in the index,
; sorts this array by a given column name in the index file
;
; @param results {in}{required}{type=array} integer array representing some rows in the index file
; @param column {in}{required}{type=string} must be uniquely identify a column in the index file
;
;-
FUNCTION IO_SDFITS::sort_search_results, results, column
compile_opt idl2, hidden
column = strtrim(strupcase(column),2)
; make sure this is a valid column name
if not self.index->validate_column_names(column) then begin
message, "Cannot sort search results; column name invalid: "+column, /info
return, results
endif
; sort the results - first get the values for the column in question
values = self.index->get_column_values( column, subset=results)
sorted_values = values[sort(values)]
sorted_results = make_array(n_elements(values),/long)
used = -1
if self.debug then print, "values: ", values
; reorder the results according to the sorted values array
for i=0,n_elements(values)-1 do begin
cnt = 0
ind = where(sorted_values[i] eq values, cnt)
if self.debug then print, "sorted_values[i]: ", sorted_values[i]
if self.debug then print, "are found at: ", ind
if cnt gt 1 then begin
; this sorted value is not unique, have we used any of these values yet?
if n_elements(used) eq 1 then if used eq -1 then first=1 else first=0
if first then begin
; no, just use the first value for the sorted result
sorted_results[i] = results[ind[0]]
used = [ind[0]]
if self.debug then print, "first used: ", used
endif else begin
; we have used some of these values already
; use just the first one that hasn't been placed in the sorted result
if self.debug then print, "used: ", used
next_not_used = ind[n_elements(used)]
if self.debug then print, "next_not_used: ", next_not_used
sorted_results[i] = results[next_not_used]
used = [used,next_not_used]
; was this the last one?
if n_elements(used) eq n_elements(ind) then used = -1
endelse
endif else begin
; this sorted value is unique, we can place it in the sorted results
sorted_results[i] = results[ind]
used = -1
endelse
endfor
return, sorted_results
END
;+
; Given a fits file name of form '/path/filename.fits', returns the expected name
; of its index: '/path/filename.index'
; @param fits_obj {in}{required}{type=object} fits object for fits file
; @returns expected name of index for given fits object
;-
FUNCTION IO_SDFITS::get_expected_full_index_name, fits_obj
compile_opt idl2
full_file_name = fits_obj->get_full_file_name()
fits_file_name = fits_obj->get_file_name()
parts = strsplit(full_file_name,"/",/extract)
; check for absolute vs. relative paths
leading_char = strmid(full_file_name,0,1)
if leading_char ne "/" then leading_char = ""
if n_elements(parts) gt 1 then begin
path = leading_char+strjoin(parts[0:n_elements(parts)-2],"/")
endif else begin
path = ""
endelse
parts = strsplit(fits_file_name,".",/extract)
if n_elements(parts) gt 1 then begin
index_file_name = strjoin(parts[0:n_elements(parts)-2],".")+".index"
endif else begin
index_file_name = fits_file_name + ".index"
endelse
if path ne "" then index_file_name = path + "/" + index_file_name
return, index_file_name
END
;+
; Instead of adding rows to an index file by reading in an sdfits file, this
; method attempts to take advantage of a pre-existing index files info.
; @param index_name {in}{required}{type=string} full path name to index file to read
; @returns 0 - failure, 1 - success
;-
FUNCTION IO_SDFITS::update_index_with_other_index, index_name
compile_opt idl2
; double check that the additional index file can be found
if not self->file_exists(index_name) then begin
message, "File does not exist, cannot use to update: "+index_name, /info
return, 0
endif
; create a temporary index object for this
other_index = obj_new(self->get_index_class_name(),file_name=index_name,version=self.version)
; read in the additional index file
other_index->read_file, ver_status
; HACK HACK HACK: had to add this step after we removed the file path from the index header.
other_index->set_file_path, self.file_path
; make sure it's valid
if ver_status eq 0 then begin
message, "Cannot use fits files index, version is out of date: "+index_name, /info
if obj_valid(other_index) then obj_destroy, other_index
return, 0
endif
if (other_index->check_index_with_reality() eq 0) then begin
message, 'Cannot use fits files index, is not up to date: '+index_name ,/info
if obj_valid(other_index) then obj_destroy, other_index
return, 0
endif
print, "Updating directory index with ", index_name
; transfer this additional index file's info to our internal index object
if not self.index->update_file_with_row_structs(other_index->get_row_structs()) then begin
return, 0
endif
self.index->read_file
self.index_synced = 1
; cleanup
if obj_valid(other_index) then obj_destroy, other_index
return, 1
END
;+
; Takes a file named *.fits and returns a string equal to *.index
; @param fits_name {in}{required}{type=string} fits file name
; @returns name of the file passed in, with extension changed to index
;-
FUNCTION IO_SDFITS::get_index_name_from_fits_name, fits_name
compile_opt idl2, hidden
parts=strsplit(fits_name,'.',/extract)
index_file = strjoin(parts[0:n_elements(parts)-2],'.')+'.index'
return, index_file
END
;+
; Sets a flagging rule for a given scan(s) along with other options
; according to the current flagging version.
; With the current version, the flagging options include:
; intnum, fdnum, plnum, ifnum, bchan, echan, and idstring.
; @param scan {in}{required}{type=long} scan or scans to be flagged.
;-
PRO IO_SDFITS::set_flag, scan, _EXTRA=ex
compile_opt idl2, hidden
if not self->check_flag_param_syntax(_EXTRA=ex) then begin
message, "Could not set flag.",/info
return
endif
scan = self->get_scans_in_index(scan, cnt)
if cnt eq 0 then begin
message, "Could not set flag.",/info
return
endif
if not self.flags->check_channels_range(_EXTRA=ex) then begin
message, "Could not set flag.",/info
return
endif
files = self->get_flag_file_names(count, scan=scan) ;, _EXTRA=ex)
if count ne 0 then begin
; for each file, set the flags
for i=0,n_elements(files)-1 do begin
self.flags->set_flag, files[i], scan, _EXTRA=ex
endfor
endif
END
;+
; Sets a flagging rule for a given record number(s) along with other options
; according to the current flagging version.
; With the current version, the flagging options include:
; bchan, echan, and idstring.
; @param recnum {in}{required}{type=long} recnum or recnums to be flagged.
;-
PRO IO_SDFITS::set_flag_rec, recnum, _EXTRA=ex
compile_opt idl2, hidden
if not self->check_flag_param_syntax(_EXTRA=ex) then begin
message, "Could not set flag.",/info
return
endif
recnum = self->get_recnums_in_index(recnum, cnt)
if cnt eq 0 then begin
message, "Could not set flag.",/info
return
endif
if not self.flags->check_channels_range(_EXTRA=ex) then begin
message, "Could not set flag.",/info
return
endif
files = self->get_flag_file_names(count, recnum=recnum)
if count ne 0 then begin
; for each file, set the flags
for i=0,n_elements(files)-1 do begin
self.flags->set_flag_rec, files[i], recnum, _EXTRA=ex
endfor
endif
END
;+
; Given a set of scan numbers, returns which scans can currently
; be found in the index file.
; @param scans {in}{required}{type=long} scan num(s) to search for
; @param count {out}{optional}{type=long} the num of scans found in index
; @keyword quiet {in}{optional}{type=bool} inform the user when scan is missing?
; @returns an array of the scans that were found in the index file.
;-
FUNCTION IO_SDFITS::get_scans_in_index, scans, count, quiet=quiet
compile_opt idl2, hidden
count = 0
for i=0,n_elements(scans)-1 do begin
row_info = self.index->search_for_row_info(scan=scans[i])
if size(row_info,/dim) ne 0 then begin
if count eq 0 then index_scans=scans[i] else $
index_scans=[index_scans,scans[i]]
count += 1
endif else begin
if keyword_set(quiet) eq 0 then begin
message, "Scan not found: " + strtrim(scans[i],2),/info
endif
endelse
endfor
if count eq 0 then return, 1 else return, index_scans
END
;+
; Given a set of record numbers, returns which records can currently
; be found in the index file.
; @param recnums {in}{required}{type=long} record num(s) to search for
; @param count {out}{optional}{type=long} the num of records found in index
; @keyword quiet {in}{optional}{type=bool} inform the user when rec is missing?
; @returns an array of the recs that were found in the index file.
;-
FUNCTION IO_SDFITS::get_recnums_in_index, recnums, count, quiet=quiet
compile_opt idl2, hidden
count = 0
for i=0,n_elements(recnums)-1 do begin
row_info = self.index->search_for_row_info(index=recnums[i])
if size(row_info,/dim) ne 0 then begin
if count eq 0 then index_recnums=recnums[i] else $
index_recnums=[index_recnums,recnums[i]]
count += 1
endif else begin
if keyword_set(quiet) eq 0 then begin
message, "Recnum not found: "+string(recnums[i]),/info
endif
endelse
endfor
if count eq 0 then return, 1 else return, index_recnums
END
;+
; When certain spectra are being flagged, which flag files need to be used?
; This can be determined by finding in which fits file the record and scan
; numbers being flagged reside.
; @param count {out}{optional}{type=long} number of flag files found
; @keyword recnum {in}{optional}{type=long} record number(s) to look for
; @keyword scan {in}{optional}{type=long} scan number(s) to look for
; @returns arrary of flag file names.
;-
FUNCTION IO_SDFITS::get_flag_file_names, count, recnum=recnum, scan=scan ;, intnum=intnum
compile_opt idl2, hidden
; which fits files in the index actually contain spectra that match our criteria?
row_info = self.index->search_for_row_info(index=recnum, scan=scan) ;, int=intnum)
if size(row_info,/dim) eq 0 then begin
message, "Search for data to flag failed.",/info
count = 0
return, -1
endif
all_files = row_info.file
files = all_files[uniq(all_files,sort(all_files))]
count = n_elements(files)
return, files
END
;+
; For printing flag files' contents
;-
PRO IO_SDFITS::list_flags, _EXTRA=ex
compile_opt idl2, hidden
self.flags->list, _EXTRA=ex
END
;+
; Prints all unique IDSTRINGS in flag files
;-
PRO IO_SDFITS::list_flag_ids
compile_opt idl2, hidden
self.flags->list_ids
END
;+
; Returns contents of all flag files
; idstring keyword allows retrieving only specific IDSTRINGs
; @param count {out}{optional}{type=long} number of lines returned
; @returns string array representing contents of all flag files
;-
FUNCTION IO_SDFITS::get_flag_lines, count, _EXTRA=ex
compile_opt idl2, hidden
return, self.flags->get_index_value_lines(count, _EXTRA=ex)
END
;+
; Removes the flag(s) associated with the id passed in. If the id passe in is
; a string, then this is an IDSTRING, and whatever flags using this IDSTRING are
; removed. If id passed in is an integer, then this is a unique ID, and only
; one flag is removed.
; @param id {in}{required}{type=string,long} ID or IDSTRING of flag to
; remove.
; @keyword all {in}{optional}{type=boolean} When set, unflag all IDs.
; @keyword quiet {in}{optopnal}{type=boolean} When set, suppress the
; warning message when the requested ID was not found.
; Any value of ID is ignored.
;-
PRO IO_SDFITS::unflag, id, all=all, quiet=quiet
compile_opt idl2, hidden
self.flags->unflag, id, all=all, quiet=quiet
END
;+
; Finds where multiple values occur in an array.
; @param search_arr {in}{required} array to be searched
; @param values {in}{required} the values to search array for
; @param total_count {out}{optional}{type=long} the total number of times each value is found in the search array
; @returns the indicies showing where the values were found in the search array.
;-
FUNCTION IO_SDFITS::find_exact_matches, search_arr, values, total_count
compile_opt idl2
total_count = 0
count = 0
for i=0, (n_elements(values)-1) do begin
value = values[i]
temp_inds = where(search_arr eq value, count)
if (count ne 0) then begin
if (n_elements(indicies) eq 0) then indicies = temp_inds else indicies = [indicies,temp_inds]
total_count = total_count + count
endif
endfor
if (total_count eq 0) then indicies = -1
return, indicies
END
;+
; Used for making a search in an array and ANDing the results with previous searches
; @examples
; ; >a = [1,2,3,4] ; >b = ['dog','cat','mouse','moose'] ; >;init the search results ; >and_result = [0,1,2,3] ; >io->find_values_plus_and,a,'1:',and_result ; >print,and_result ; >[1,2,3] ; >io->find_values_plus_and,b,'dog,mo*',and_result ; >print,and_result ; >[2,3] ;; @private ;- PRO IO_SDFITS::find_values_plus_and, array, values, and_result compile_opt idl2 if n_elements(and_result) eq 1 then begin if and_result eq -1 then return endif values_result = self->find_exact_matches(array[and_result], values, status) if n_elements(values_result) eq 1 then begin if values_result eq -1 then begin and_result = -1 endif else begin and_result = and_result[values_result] endelse endif else begin and_result = and_result[values_result] endelse END ;+ ; For a given flag structure, determines which of the given data containers' this ; flag applies to. This is done by checking the flags contents against the data ; containers' scan_number, integration, polarization_num, feed_num, and if_number ; fields. ; @param dcs {in}{required}{type=data container array} data containers to check ; @param flag {in}{required}{type=flag structure} structure representing a line in a flag file ; @param count {out}{optional}{type=long} number of data containers that the flag applies to ; @returns indicies of the data containers that the flag applies to, -1 if none. ;- FUNCTION IO_SDFITS::find_flagged_data, dcs, flag, count compile_opt idl2, hidden result = lindgen(n_elements(dcs)) if flag.scan eq '*' and flag.intnum eq '*' then begin count = 0 return, -1 endif result = self->find_flagged_data_field(dcs.scan_number, flag, "scan", result) result = self->find_flagged_data_field(dcs.integration, flag, "intnum", result) result = self->find_flagged_data_field(dcs.polarization_num, flag, "plnum", result) result = self->find_flagged_data_field(dcs.feed_num, flag, "fdnum", result) result = self->find_flagged_data_field(dcs.if_number, flag, "ifnum", result) if n_elements(result) eq 1 then begin if result eq -1 then count=0 else count=1 endif else begin count = n_elements(result) endelse return, result END ;+ ; Looks for matches between a given array of values, and a field in a given ; flagging structure. The flag structure holds strings, which magy need to ; be converted to integer arrays before the search can be done. ; @param data_field {in}{required}{type=array} array to be searched ; @param flag_strct {in}{required}{type=flag structure} flag structure ; @param flag_field_name {in}{required}{type=string} the name of the field in the flag structure whose value will be matched in the search array ; @returns the indicies where the flag structure's field appears in the search array ;- FUNCTION IO_SDFITS::find_flagged_data_field, data_field, flag_strct, flag_field_name, result compile_opt idl2, hidden ; check to make sure that the flag structure contains the flag field name flag_field_name = strupcase(flag_field_name) tags = tag_names(flag_strct) ind = where(tags eq flag_field_name, count) if count eq 0 then begin return, result endif else begin flag_field = flag_strct.(ind) endelse if flag_field eq "*" then begin return, result endif else begin flag_field_ints = self.flags->get_int_array_from_flag_string(flag_field) self->find_values_plus_and,data_field, flag_field_ints, result return, result endelse END ;+ ; Once it has been determined which flags apply to which data containers, this ; method actually blanks the data containers data, using the bchan and echan ; fields. ; @param dcs {in}{required}{type=data container array} array of data contianers to be flagged by single flag ; @param flag {in}{required}{type=flag structure} structure representing a line in a flag file ;- PRO IO_SDFITS::flag_data, dcs, flag compile_opt idl2, hidden for i=0,n_elements(dcs)-1 do begin dc = dcs[i] len = n_elements(*dc.data_ptr) ; channels can cover multiple ranges uses_range = strpos(flag.bchan,",") if uses_range eq -1 then begin if flag.bchan eq '*' then begin bchan=0 endif else begin bchan=long(flag.bchan) if bchan gt len then bchan = len-1 if bchan lt 0 then bchan = 0 endelse if flag.echan eq '*' then begin echan=len-1 endif else begin echan=long(flag.echan) if echan gt len then echan = len-1 if echan lt 0 then echan = 0 endelse if bchan gt echan then begin tmp = echan echan = bchan bchan = tmp endif ; finally, blank the data (*dc.data_ptr)[bchan:echan] = !values.f_nan endif else begin ; the channels are given in multiple ranges branges = strsplit(flag.bchan,",",/extract) eranges = strsplit(flag.echan,",",/extract) ; assume the beginnings and ending arrays are of same length for j=0,n_elements(branges)-1 do begin bchan = long(branges[j]) echan = long(eranges[j]) if bchan gt echan then begin tmp = echan echan = bchan bchan = tmp endif if bchan lt 0 then bchan = 0 if bchan ge len then bchan = (len-1) if echan lt 0 then echan = 0 if echan ge len then echan = (len-1) ; finally, blank the data (*dc.data_ptr)[bchan:echan] = !values.f_nan endfor ; each channel range endelse ; uses range or not endfor ; each data container END ;+ ; Find if the specific scan has been flagged at all. ; ; @param scan {in}{required}{integer} Scan number to check. ; @keyword idstring {in}{optional}{string}{default all ids} If given, ; only find if the scan has been flagged with that idstring. ; @returns 1 if it has been flagged in some way, otherwise 0. ;- FUNCTION IO_SDFITS::IS_SCAN_FLAGGED, scan, idstring=idstring compile_opt idl2 ; if we're online, read in the latest index rows into memory if self.online then self->update flags = self.flags->get_flag_strcts(count,indxRec,useflag=idstring) if count eq 0 then return, 0 scanRec = -1 ; record flagging is expensive to check for j=0,n_elements(indxRec)-1 do begin if indxRec[j] ne '*' then begin ; there is actually something there to expand recnums = decompress_ints(indxRec[j]) if scanRec[0] eq -1 then begin ; get indices for this scan indx = self->get_index_values('index') allScans = self->get_index_values('scan') wScan = where(allScans eq scan, count) ; if that scan isn't found, it can't be flagged, give up ; could give up completely here, but check specific scan ; content of the flags themselves - might be confusing otherwise if count eq 0 then break scanRec = indx[wScan] endif ; combine the scan records with the flag records result = lindgen(n_elements(scanRec)) self->find_values_plus_and, scanRec, recnums, result if result[0] ne -1 then begin ; this scan is flagged, can stop now return, 1 endif endif end ; check for this scan for j=0,n_elements(flags)-1 do begin if flags[j].scan ne '*' then begin flaggedScans = decompress_ints(flags[j].scan) w = where(flaggedScans eq scan,count) if count gt 0 then begin ; it is flagged return, 1 endif endif end return,0 END ;+ ; Get the named columns as a set of vectors in a structure from ; the associated data file(s) using any supplied selection criteria. ; ; @param columns {in}{required}{type=string array} The list of columns ; to fetch. Column names are not the same as the names found in the ; data container. The column name must match the name as found in the ; associated FITS file(s) (including case). ; There will be a field named "missing" that will list any missing columns. ; @keyword _EXTRA {in}{optional}{type=structure} see search_for_row_info for more info ; @returns structure - one field for each requested column. Returns ; -1 on any errors. ;- FUNCTION IO_SDFITS::get_columns, columns, _EXTRA=ex compile_opt idl2 if n_elements(columns) eq 0 then begin message,'columns is required',level=-1,/info return,-1 endif if n_elements(columns) le 0 then begin message,'columns must contain at least one named column',level=-1,/info return,-1 endif if size(columns,/type) ne 7 then begin message,'columns must be a string or array of strings',level=-1,/info return,-1 endif if self.index->validate_search_keywords(ex) eq 0 then begin message,'invalid selection keywords',level=-1,/info return, -1 endif ; if we're online, read the latex index rows into memory if self.online then self->update ; find the files,extensions, and rows that match the specified criteria row_info = self.index->search_for_row_info(indicies, _EXTRA=ex) if (size(row_info,/dimension) eq 0) then begin if row_info eq -1 then begin count = 0 print,'nothing there' return, -1 endif endif ; group rows found in index file by filename and extension groups = self->group_row_info(row_info) ; construct outline of the structure nrows = n_elements(indicies) result = -1 missingCols = -1 ; must read each extension seperately ; because they may contain varying ; data sizes startRow = 0 actualCols = -1 for i = 0, n_elements(groups)-1 do begin ; open the appropriate file thisGroup = groups[i] ext = thisGroup.extension thisFits = self->get_fits(thisGroup.file) thisFitsPath = thisFits->get_full_file_name() errmsg = "" fxbopen, fun, thisFitsPath, ext, hdr, /no_tdim, errmsg=errmsg if errmsg ne '' then begin ; some error, give up and return as if everything is missing self->free_group_row_info, groups return,create_struct('missing',columns) endif if size(actualCols,/type) ne 7 then begin ; columns with "_" may need to be seen as "-" if not found for j=0, n_elements(columns)-1 do begin thisCol = columns[j] actualCol = thisCol undPos = strpos(thisCol,'_') if undPos ge 0 then begin if fxbcolnum(fun,thisCol,errmsg=errmsg) le 0 then begin while undPos ge 0 do begin strput,thisCol,'-',undPos undPos = strpos(thisCol,'_') endwhile if fxbcolnum(fun,thisCol,errmsg=errmsg) gt 0 then begin actualCol = thisCol endif endif endif if j eq 0 then begin actualCols = [actualCol] endif else begin actualCols = [actualCols,actualCol] endelse endfor endif theseRows = *(thisGroup.rows) minRow = min(theseRows) maxRow = max(theseRows) warnmsg = "" status = -1 fxbreadm, fun, actualCols, row=[minRow+1,maxRow+1], pass_method='pointer', pointers=pntrs, errmsg=errmsg, warnmsg=warnmsg, status=status ; in any event, done reading, can close fxbclose, fun if errmsg ne '' then begin ; some fatal error, give up and return ; as if everything is missing self->free_group_row_info, groups ptr_free, pntrs return,create_struct('missing',columns) endif rowOffsets = theseRows-minRow if size(result,/type) ne 8 then begin ; construct structure using existing ; type info of returned rows for j = 0, n_elements(columns)-1 do begin thisCol = columns[j] if status[j] eq 1 then begin ; this column exists thisPtr = pntrs[j] colVal = (*thisPtr)[0] colArray = make_array(nrows,value=colVal[0]) if size(result,/type) ne 8 then begin ; first field result = create_struct(thisCol,colArray) endif else begin result = create_struct(thisCol,colArray, result) endelse endif else begin ; this column is missing - also ; accompanied by a warnmsg, ; which we're ignoring. if size(missingCols,/type) ne 7 then begin ; first example missingCols = [thisCol] endif else begin missingCols = [missingCols,thisCol] endelse endelse endfor ; the case for all columns not being ; found generates an errmsg and is ; caught above resultCols = tag_names(result) endif nNewRows = n_elements(theseRows) endRow = startRow + nNewRows - 1 for j=0, n_elements(columns)-1 do begin if status[j] eq 1 then begin thisPtr = pntrs[j] if n_elements(rowOffsets) eq (maxRow-minRow+1) then begin theseValues = (*thisPtr) endif else begin theseValues = (*(thisPtr))[rowOffsets] endelse thisCol = columns[j] colIndx = where(resultCols eq thisCol) resultVal = result.(colIndx) resultVal[startRow:endRow] = theseValues result.(colIndx) = resultVal endif ; else it's missing columns endfor startRow = startRow + nNewRows ; delete the returned ptrs from fxbreadm ptr_free, pntrs endfor ; add in the 'missing' field result = create_struct('missing',missingCols,result) self->free_group_row_info, groups return,result end ;+ ; For finding what data containers have been flagged via record number. ; Finds matches between a given array of index record numbers, and a string containing ; a compressed list of index numbers. ; @param indicies {in}{required}{type=long} array of index record numbers ; @param recnum_string {in}{required}{type=string} compressed list of index record numbers to match against search array ; @param count {out}{optional}{type=long} number of matches found ; @returns the indicies where a record number in the compressed string is found in the search array ;- FUNCTION IO_SDFITS::find_flagged_index_data, indicies, recnum_string, count compile_opt idl2, hidden ; if the record number was not used, nothing to search for if recnum_string eq '*' then begin count = 0 return, -1 endif ; convert the string passed into an integer array of record numbers recnums = decompress_ints(recnum_string) ; search for matches result = lindgen(n_elements(indicies)) self->find_values_plus_and, indicies, recnums, result if n_elements(result) eq 1 then begin if result eq -1 then count=0 else count=1 endif else begin count = n_elements(result) endelse return, result END ;+ ; For testing purposes only. Allows overriding of flag format to use ;- PRO IO_SDFITS::set_flag_file_version, version_num, version_class compile_opt idl2, hidden if obj_valid(self.flags) then self.flags->set_flag_file_version, version_num, version_class END ;+ ; Checks a given value against a given type ; @param value {in}{required} value to be checked ; @param type {in}{required}{type=string} type ; @param name {in}{required}{type=string} name of the keyword being checked. ; @keyword quiet {in}{optional}{type=bool} quiet on errors? ; @returns 0 - not valid, 1 - valid ;- FUNCTION IO_SDFITS::validate_param_value_type, value, type, name, quiet=quiet compile_opt idl2, hidden value_type = size(value, /type) valid = 1 case type of "string": if value_type ne 7 then valid = 0 "integer": if value_type ne 2 and value_type ne 3 then valid = 0 "float": if value_type ne 4 and value_type ne 5 then valid = 0 "scalar_integer": if (value_type ne 2 and value_type ne 3) or n_elements(value) ne 1 then valid = 0 endcase if valid then begin return, 1 endif else begin if keyword_set(quiet) eq 0 then print, name+" must be of type "+type return, 0 endelse END ;+ ; Checks to make sure that the given value is one of the given types. If its ; a string, it also makes sure that is is in the correct form. This is used ; to make sure that keywords for index searches and flagging commands are ; correctly used. ; @param value {in}{required} value to be checked ; @param types {in}{required}{type=string} comma separated types ; @param name {in}{required}{type=string} name of the keyword being checked. ; @keyword quiet {in}{optional}{type=bool} quiet on errors? ; @returns 0 - not valid, 1 - valid ;- FUNCTION IO_SDFITS::validate_param_value_types, value, types, name, quiet=quiet compile_opt idl2, hidden one_valid = 0 value_type = size(value, /type) type_elements = strsplit(types,",",/extract) for i=0,n_elements(type_elements)-1 do begin valid = self->validate_param_value_type(value,type_elements[i],name,/quiet) if valid then one_valid = 1 endfor if one_valid ne 1 then begin if keyword_set(quiet) eq 0 then $ print, name+" must be of one of the following types: ", types return, 0 endif if value_type eq 7 then begin if not self->validate_string_value(value, types, /quiet) then begin if keyword_set(quiet) eq 0 then $ print, name+" does not contain a valid search range: ", value return, 0 endif endif return, 1 END ;+ ; Generic method used for checking keywords used in both flagging commands and ; index searches. The names and types for each keyword are stored in the 2-D ; string arrays in the flag and index objects. These arrays are passed in to ; this method, along with the keywords used (passed in as a structure). ; In this method, for each keyword used, first the keyword is checked if it's ; name is valid, and then its type and range syntax is checked. ; @param values_strct {in}{required}{type=structure} keywords passed in via inheritance ; @param param_types {in}{required}{type=string array} contains param names and their types ;- FUNCTION IO_SDFITS::validate_param_list_types, values_strct, param_types, _EXTRA=ex compile_opt idl2, hidden ; deconstruct the parameter names and types ; how many params? sz = size(param_types) np = sz[2] param_names = param_types[0,0:np-1] param_types = param_types[1,0:np-1] for i=0,n_elements(param_names)-1 do begin param_names[i] = strupcase(param_names[i]) endfor value_names = tag_names(values_strct) ; validate each keyword passed down list_valid = 1 for i=0,n_elements(value_names)-1 do begin name = value_names[i] value = values_strct.(i) ;param_ind = where(param_names eq name, cnt) param_ind = self->get_param_index(param_names, name, cnt) if cnt eq 0 then begin if keyword_set(quiet) eq 0 then print, "Parameter not a valid name: ", name return, 0 endif types = param_types[param_ind] valid = self->validate_param_value_types(value,types,name,_EXTRA=ex) if not valid then list_valid = 0 endfor return, list_valid END ;+ ; Looks for a keyword passed in for such methods as index searches or ; or flag commands in the list of valid names. A keyword shortened to ; it's most unambigious abbreviation is valid. ; @param param_names {in}{required}{type=string array} list of valid names ; @param param_name {in}{required}{type=string} parameter name, may be abbreviated ; @param count {out}{optional}{type=long} number of times name is found ; @returns the index of the keyword in the list of valid names ;- FUNCTION IO_SDFITS::get_param_index, param_names, param_name, count compile_opt idl2, hidden for i=0,n_elements(param_names)-1 do param_names[i]=strupcase(param_names[i]) itsparam = strtrim(strupcase(param_name),2) paramregex = '^' + itsparam + '.*' index = where(stregex(param_names,paramregex,/boolean),count) return, index END FUNCTION IO_SDFITS::check_param_syntax, param_name, value, _EXTRA=ex compile_opt idl2, hidden ;if self.index->validate_search_keywords(ex) eq 0 then begin ; count = 0 ; return, -1 ;endif param_types = self->get_single_param_types(param_name) if param_types eq "" then begin if keyword_set(quiet) eq 0 then $ print, param_name+" is not a valid parameter name." return, 0 endif else begin return, self->validate_param_value_types(value,param_types,param_name,_EXTRA=ex) endelse END FUNCTION IO_SDFITS::get_single_param_types, param_name compile_opt idl2, hidden param_types = self->get_index_param_types() ; deconstruct the parameter names and types ; how many params? sz = size(param_types) np = sz[2] param_names = param_types[0,0:np-1] param_types = param_types[1,0:np-1] for i=0,n_elements(param_names)-1 do begin param_names[i] = strupcase(param_names[i]) endfor param_name = strupcase(param_name) ind = where(param_names eq param_name, cnt) if cnt eq 0 then begin return, "" endif return, param_types[ind] END ;+ ; All the index file columns can be searched against, using keyword arguments. ; The types of each column are stored in memory in the index object, which this ; method retrieves. This way, the keywords used in a search can be checked FUNCTION IO_SDFITS::get_index_param_types compile_opt idl2, hidden if obj_valid(self.index) then begin return, self.index->get_param_types() endif else begin return, -1 endelse END ;+ ; Most of the keywords used in a flagging command match against the columns found ; in a flag file. These columns, along with the CHANS and CHANWIDTH columns have ; their names and types stored in a 2-D string array in the flag object. This ; method retrieves this info so that the keywords used with a flagging command ; can be checked for valid type and range syntax before the command progresses. ;- FUNCTION IO_SDFITS::get_flag_param_types compile_opt idl2, hidden if obj_valid(self.flags) then begin return, self.flags->get_param_types() endif else begin return, -1 endelse END ;+ ; Checks the parameters used in a flagging command ; Checks for validity of both type and range syntax. ; First the param types for a flagging commmand are found, which are stored ; as a 2-D array in the flag object, and then these values are used to ; check against the search parameters actually used. ; _EXTRA=ex refers to all keywords valid in a flagging command. ; @keyword quiet {in}{optional}{type=bool} wether or not to be quiet on errors ;- FUNCTION IO_SDFITS::check_flag_param_syntax, _EXTRA=ex, quiet=quiet compile_opt idl2, hidden ; could be called with no search parameters if n_elements(ex) eq 0 then return, 1 if n_elements(quiet) eq 0 then quiet = 0 param_types = self->get_flag_param_types() result = self->validate_param_list_types(ex, param_types, quiet=quiet) ; nifty trick to inherit keywords if 0 then self.flag->set_flag, 0, _EXTRA=ex return, result END ;+ ; Checks the parameters used for a search, be it for a list command, or get_* ; method. Checks for validity of both type and range syntax. ; First the param types for an index file search are found, which are stored ; as a 2-D array in the index object, and then these values are used to ; check against the search parameters actually used. ; _EXTRA=ex refers to all keywords valid for a search. ; @keyword quiet {in}{optional}{type=bool} wether or not to be quiet on errors ;- FUNCTION IO_SDFITS::check_search_param_syntax, _EXTRA=ex, quiet=quiet compile_opt idl2, hidden ; could be called with no search parameters if n_elements(ex) eq 0 then return, 1 if n_elements(quiet) eq 0 then quiet = 0 param_types = self->get_index_param_types() result = self->validate_param_list_types(ex, param_types, quiet=quiet) ; nifty trick to inherit keywords if 0 then self.index->search_index, _EXTRA=ex return, result END ;+ ; Used to check the validaty of values passed into keywords used with index and ; flagging commands. Integer and float ranges can sometimes be passed in with ; string values, and strings themselves sometimes can use wildcards. ; @param value {in}{required}{type=string} value to be validated ; @param types {in}{required}{type=string} comma separated types ; @returns 0 - not valid, 1 - valid ;- FUNCTION IO_SDFITS::validate_string_value, value, types, quiet=quiet compile_opt idl2, hidden case types[0] of "integer,string": valid = self->validate_integer_range(value) "float,string": valid = self->validate_float_range(value) "string": valid = self->validate_string_range(value) end return, valid END ;+ ; Sometimes keywords that take integers can be passed in a string representing a ; a range of integers. This checks that the value ; passed in conforms to the correct syntax. ; @param value {in}{required}{type=string} value to be checked ; @returns 0 - does not conform to syntax, 1 - does conform ;- FUNCTION IO_SDFITS::validate_integer_range, value compile_opt idl2, hidden ON_IOERROR, non_integer ; get rid of leading and trailing whitespace value = strtrim(value,2) ; special case if value eq ":" then return, 0 ; go through each range ranges = strsplit(value,",",/extract) for i=0,n_elements(ranges)-1 do begin range = ranges[i] ; determine the type of range, and find results colon_pos = strpos(range,":") count = 0 case colon_pos of 0: begin ; less then limit = long(strmid(range,1,strlen(range)-1)) end strlen(range)-1: begin ; greater then limit = long(strmid(range,0,strlen(range)-1)) end -1: begin ; simple integer limit = long(range) end else: begin ; range limits = strsplit(range,":",/extract) low_limit = long(limits[0]) up_limit = long(limits[1]) end endcase endfor return, 1 non_integer : return, 0 END ;+ ; Sometimes keywords that take floats can be passed in a string representing a ; float to a certain perscion, or a range of floats. This checks that the value ; passed in conforms to the correct syntax. ; @param value {in}{required}{type=string} value to be checked ; @returns 0 - does not conform to syntax, 1 - does conform ;- FUNCTION IO_SDFITS::validate_float_range, value compile_opt idl2, hidden ON_IOERROR, non_float ; get rid of leading and trailing whitespace value = strtrim(value,2) ; special case if value eq ":" then return, 0 ; go through each range ranges = strsplit(value,",",/extract) for i=0,n_elements(ranges)-1 do begin range = ranges[i] ; determine the type of range, and find limits colon_pos = strpos(range,":") count = 0 case colon_pos of 0: begin ; less then limit = double(strmid(range,1,strlen(range)-1)) end strlen(range)-1: begin ; greater then limit = double(strmid(range,0,strlen(range)-1)) end -1: begin ; simple float - treat like a small range parts = strsplit(range,'.',/extract) base_range = double(range) if (n_elements(parts) eq 1) then begin offset = 0.5 endif else begin precision = strlen(parts[1]) offset = 5.0/(10^(precision+1)) endelse low_limit = base_range - offset up_limit = base_range + offset end else: begin ; range limits = strsplit(range,":",/extract) low_limit = double(limits[0]) up_limit = double(limits[1]) end endcase endfor return, 1 non_float : return, 0 END ;+ ; For some keywords, strings can be used with wildcards either at the begining ; or end of the string. This makes sure that the passed in string conforms ; to this syntax ; @param values {in}{required}{type=string array} values to be checked ; @returns 0 - does not conform to syntax, 1 - does conform ;- FUNCTION IO_SDFITS::validate_string_range, values compile_opt idl2 ON_IOERROR, bad_string ; go through each value for i=0,n_elements(values)-1 do begin value = values[i] value = strtrim(value,2) count = 0 ; look for a wildcard wildcard_pos = strpos(value,'*') case wildcard_pos of 0: begin ; wildcard at begining ; we are now only looking for string that match with the last n chars search_value = strmid(value,1) end strlen(value)-1: begin ; wildcard at end ; we are now only looking for string that match with the first n chars search_value = strmid(value,0,strlen(value)-1) end else: begin if wildcard_pos ne -1 then begin ; wildcards only allowed at beginning and end return, 0 endif end endcase endfor return, 1 bad_string : return, 0 END ;+ ; Retrieves the last record, or index number to be retrieved from a get_* method ; @returns last record member variable ;- FUNCTION IO_SDFITS::get_last_record compile_opt idl2 return, self.last_record END ;+ ; Get access to the associated flags object ;- FUNCTION IO_SDFITS::get_flags_obj compile_opt idl2 return, self.flags END ;+ ; Debugging function to explore the FLAG state ;- PRO IO_SDFITS::flags_show_state compile_opt idl2 print,"IO_SDFITS flag state, FITS files and associated FLAG file" files = self->get_fits_file_names() if size(files,/type) ne 7 then begin print,"no valid FITS files yet" return endif for i=0,n_elements(files)-1 do begin print," ", files[i], " --> ", self.flags->fits_filename_to_flag_filename(files[i]) endfor print,"END IO_SDFITS flag state" self.flags->show_state END