;+ ; IO_SDFITS_LINE is intended for end users wishing to work with spectral line data. It's the child class of IO_SDFITS used for reading, writing, ; navigating sdfits spectrual line files, and for ; translating their info to spectrum data containers. See ; UML for all IO Classes, or ; IO_SDFITS UML for just ; the line and continuum sdfits classes. ; ; ; @file_comments ; IO_SDFITS_LINE is intended for end users wishing to work with spectral line data. It's the child class of IO_SDFITS used for reading, writing, ; navigating sdfits spectrual line files, and for ; translating their info to spectrum data containers. See ; UML for all IO Classes, or ; IO_SDFITS UML for just ; the line and continuum sdfits classes. ; ; @uses LINE_INDEX ; @uses SDFITS ; ; @inherits io_sdfits ; ; @version $Id: io_sdfits_line__define.pro,v 1.77 2011/04/14 16:41:04 bgarwood Exp $ ;- ;+ ; defines class structure ; @private ;- PRO io_sdfits_line__define compile_opt idl2, hidden @online_status_info io = { io_sdfits_line, inherits io_sdfits, $ index_class_name:'', $ sdfits_class_name:'', $ default_index_name:'', $ online_dir:string(replicate(32B,256)), $ online_info:{online_status_info_strct} $ ; online_lock_dir:string(replicate(32B,256)) $ } END ;+ ; Called upon instantiation of this class. ; @uses IO_SDFITS::init ; @private ;- FUNCTION IO_SDFITS_LINE::init,index_file=index_file compile_opt idl2, hidden self.index_class_name = 'line_index' self.sdfits_class_name = 'line_sdfits' self.default_index_name = 'io_sdfits_line_index' r = self->shared_init(index_file=index_file) return, r END ;+ ; Shared initialization of this class and derived classes ; @private ;- FUNCTION IO_SDFITS_LINE::shared_init, index_file=index_file compile_opt idl2, hidden r = self->IO_SDFITS::init() if r eq 1 then begin if keyword_set(index_file) then begin self.index = obj_new(self.index_class_name,file_name=index_file,version=self.version) endif else begin self.index = obj_new(self.index_class_name,file_name=self.default_index_name,version=self.version) endelse self.debug = 0 self.online_dir = getConfigValue("SDFITS_DATA",defaultValue="/home/sdfits") ; self.online_lock_dir = "/home/sdfits-locks" endif return, r END ;+ ; Class destructor ;- PRO IO_SDFITS_LINE::cleanup compile_opt idl2, hidden self->IO_SDFITS::cleanup obj_destroy,self.online_status END ;+ ; Reads in all the rows from every extension in the fits file represented by the ; passed in sdfits object, and passes this on to the index object so that the index ; file can be updated. ; Also, if an index file already exists for this fits file, this index is used ; to update the current index. If an index file doesn't already exist for this ; fits file, it is created first, and then used. ; @param fits_obj {in}{type=object} sdfits object representing an sdfits file. ; @uses SDFITS::get_and_eval_rows ; @uses SDFITS::get_number_extensions ; @uses SDFITS::get_file_name ; @uses SDFITS::get_extension_header_value ; @uses LINE_INDEX::update_file ; @private ;- PRO IO_SDFITS_LINE::update_index_with_fits_file, fits_obj compile_opt idl2 ; if a fits file is called 'name.fits', its index is 'name.index' index_name = self->get_expected_full_index_name(fits_obj) ; if we are trying to create 'name.index' from 'name.fits', ; (using self->set_file for examle), then read this fits file ; and create the index if index_name eq self.index->get_full_file_name() then begin ; must build index directly just by reading fits file self->update_index_obj_with_fits_obj, self.index, fits_obj ; update the flag files self->update_flags_with_fits_file, fits_obj endif else begin ; do we need to create a new index to help build our current index file? if not self->file_exists(index_name) then begin ; there does not exist an index file for this fits file yet self->create_index_for_fits_obj, index_name, fits_obj, status endif ; if this fits file already has an index file, use it! if self->file_exists(index_name) then begin if self->update_index_with_other_index(index_name) then begin if self.debug then print, "used fits index to update index: "+index_name ; update the flag files self->update_flags_with_fits_file, fits_obj return endif else begin if self.debug then print, "failed attempt to use fits index to update index: "+index_name ;message, "failed attempt to use fits index to update index: "+index_name file_delete, index_name self->create_index_for_fits_obj, index_name, fits_obj, status if self->update_index_with_other_index(index_name) then begin ; update the flag files self->update_flags_with_fits_file, fits_obj endif else begin message, "failed attempt to use fits index to update index: "+index_name endelse endelse endif endelse ; using self->set_file mode? self.index->read_file self.index_synced = 1 END ;+ ; Given an object that manages a fits file, and an object for the index ; file, use the fits object to update the index object. Avoids making ; the costly effort of reading the DATA column from the fits file. ; @param index_obj {in}{required}{type=object} object representing an index file ; @param fits_obj {in}{required}{type=object} object representing a fits file ;- PRO IO_SDFITS::update_index_obj_with_fits_obj, index_obj, fits_obj num_exts = fits_obj->get_number_extensions() for ext = 1, (num_exts) do begin ; for updating an index, we don't need the data column rows = fits_obj->get_and_eval_rows(missing, virtuals, ext=ext, /no_data) ; watch for bad return result from get_and_eval_rows (empty extension) if size(rows,/type) ne 8 then continue project = fits_obj->get_extension_header_value('PROJID') file_name = fits_obj->get_file_name() index_obj->update_file, rows, project, file_name, ext, missing, virtuals endfor END ;+ ; Groups a collection of rows from the index file by file and extension. ; This method is needed since we will want to access each files extension only once ; to read the pertinent rows (for efficiany reasons). ; @param row_info {in}{type=array} array of structs, where each struct represents a row of the index file ; @returns array of group_row_info structures: rows that share a file and extension ; @private ;- FUNCTION IO_SDFITS_LINE::group_row_info, row_info compile_opt idl2 ; get all files files = row_info.file unique_files = files[uniq(files[sort(files)])] group = {line_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.index = ptr_new(file_ext_locals.index) group.integrations = ptr_new(file_ext_locals.integration) group.if_numbers = ptr_new(file_ext_locals.if_number) group.feed_nums = ptr_new(file_ext_locals.feed_number) group.pol_nums = ptr_new(file_ext_locals.pol_number) group.nsaves = ptr_new(file_ext_locals.nsave) if (i eq 0) and (j eq 0) then groups = [group] else groups = [groups,group] endfor endfor return, groups END ;+ ; Function to convert rows into data containers. ; This is used internally in get_spectra. ; @private ;- FUNCTION IO_SDFITS_LINE::rows_to_dc, rows, group, missing, virtuals, apply_offsets compile_opt idl2, hidden return, self->rows_to_spectra(rows, *group.integrations, $ *group.if_numbers, *group.feed_nums, $ *group.pol_nums, *group.nsaves, $ missing, virtuals, apply_offsets) END ;+ ; This function searches the index file using the keyword parameters passed ; into it, reads the appropriate parts of the sdfits files, and tranlates this ; data into spectrum structures, which are returned. ; ; @keyword _EXTRA {in}{optional} see search_for_row_info for more info ; @param count {out}{optional}{type=long} number of spectra returned ; @param indicies {out}{optional}{type=long} index numbers ; corresponding to each returned spectrum. Can be used in flagging, ; future data retrieval, etc. ; ; @returns Array of spectrum structures ; ; @examples ;
; 
; ; @uses LINE_INDEX::search_for_row_info ; @uses SDFITS::get_and_eval_rows ; @uses IO_SDFITS_LINE::rows_to_spectra ; ;- FUNCTION IO_SDFITS_LINE::get_spectra, _EXTRA=ex, useflag=useflag, skipflag=skipflag, count, indicies compile_opt idl2 if self.index->validate_search_keywords(ex) eq 0 then begin count = 0 return, -1 endif if self->check_search_param_syntax(_EXTRA=ex) eq 0 then begin count = 0 return, -1 endif ; if we're online, read in the latest 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) ; get the flags flags = self.flags->get_flag_strcts(useflag=useflag, skipflag=skipflag, flag_count, index_value_recnums) if (size(row_info,/dimension) eq 0) then begin if row_info eq -1 then begin count = 0 return, -1 endif endif ; set this here, the spectra may not be returned in the order in which ; there were discovered via row_info, use that one to know ; what was last, indicies then is reset to reflect the returned order self.last_record = indicies[n_elements(indicies)-1] ; group rows found in index file by filename and extension groups = self->group_row_info(row_info) ; must read each extension seperately becasue they may contain varying data sizes for i = 0, n_elements(groups)-1 do begin sdfits_rows = self->get_and_eval_rows(groups[i], missing, virtuals,apply_offsets) ; watch for bad return value - empty extension if size(sdfits_rows,/type) ne 8 then continue ; convert this group of rows to data containers spectra = self->rows_to_dc(sdfits_rows, groups[i], missing, virtuals,apply_offsets) ; flag via recnum , if there ARE flags if flag_count ne 0 then begin for j=0,n_elements(flags)-1 do begin ind = self->find_flagged_index_data( *groups[i].index, index_value_recnums[j], count) if count ne 0 then self->flag_data,spectra[ind],flags[j] endfor endif if (i eq 0) then begin all_spectra = [spectra] ; save off the indicies for later use indicies = [*groups[i].index] endif else begin all_spectra = [temporary(all_spectra),spectra] indicies = [temporary(indicies),*groups[i].index] endelse endfor self->free_group_row_info, groups count = n_elements(all_spectra) ; now, apply flagging via spectral characteristics, if there ARE flags if flag_count ne 0 then begin for i=0,n_elements(flags)-1 do begin flag = flags[i] flagged_data = self->find_flagged_data(all_spectra, flag, fcount) if fcount ne 0 then self->flag_data, all_spectra[flagged_data],flag endfor endif return, all_spectra END ;+ ; Handles the translation of several sdfits rows to spectrum data containers. ; Utilizes additional info from the index file, as well as info on keywords in the extension roads are found in, and what ever expected columns were not found in the sdfits file. ; ; @param sdfits_rows {in}{type=array} an array of structures mirroring rows from an sdfits file extension containing spectral line data ; @param integrations {in}{type=array} the integration numbers for these spectra; from index file ; @param if_numbers {in}{type=array} the if numbers for these spectra; from index file ; @param nsaves {in}{type=array} the nsave numbers for these spectra; from index file ; @param missing {in}{type=array} array of column names that were expected in sdfits file extension, but not found. ; @param virtuals {in}{optional}{type=struct} structure containing keywords found in the extension header. ; ; @uses io_sdfits_line::sdfits_row_to_spectrum ; @private ;- FUNCTION IO_SDFITS_LINE::rows_to_spectra, sdfits_rows, integrations, if_numbers, feed_nums, pol_nums, nsaves, missing, virtuals, apply_offsets compile_opt idl2 @spectrum_struct num_rows = n_elements(sdfits_rows) spectra = make_array(num_rows,value={spectrum_struct}) for i = 0, (num_rows-1) do begin spectrum = self->sdfits_row_to_spectrum( sdfits_rows[i], integrations[i], if_numbers[i], feed_nums[i], pol_nums[i], nsaves[i], missing, virtuals, apply_offsets ) if self.debug then print, "getting spectrum: "+string(i)+" of: "+string(num_rows) spectra[i] = spectrum endfor return, spectra END ;+ ; Handles the translation of an sdfits row to a spectrum data container. ; Utilizes additional info from the index file, as well as info on ; keywords in the extension roads are ; found in, and what ever expected columns were not found in the sdfits file. ; ; @param row {in}{type=struct} a structure mirroring a row from an sdfits file extension containing spectral line data ; @param integration {in}{type=long} the integration number of this spectrum; from index file ; @param if_number {in}{type=long} the if number of this spectrum; from index file ; @param nsave {in}{type=long} the nsave number of this spectrum; from index file ; @param missing {in}{type=array} array of column names that were expected in sdfits file extension, but not found. ; @param virtuals {in}{optional}{type=struct} structure containing keywords found in the extension header. ; ; @uses io_sdfits::get_row_value ; @uses io_sdfits::format_sdfits_freq_type ; @uses io_sdfits::coord_mode_from_types ; @uses io_sdfits::translate_sig ; @uses io_sdfits::translate_cal ; @uses io_sdfits::parse_sdfits_obsmode ; @uses fitsdateparse ; @uses juldate ; @private ;- FUNCTION IO_SDFITS_LINE::sdfits_row_to_spectrum, row, integration, if_number, feed_num, pol_num, nsave, missing, virtuals, apply_offsets compile_opt idl2 spec = {spectrum_struct} ;data_new() spec.data_ptr = ptr_new(/allocate_heap) ; check for NO virtual columns if (size(virtuals,/TYPE) eq 3) then virtual_names=-1 else virtual_names=tag_names(virtuals) names = {row:ptr_new(tag_names(row)),missing:ptr_new(missing),virtuals:ptr_new(virtual_names)} ; default values to use for missing cols in sdfits row di = -1L df = 0.0 dd = 0.0D ds = 'default' nan = !values.f_nan spec.source = self->get_row_value(row,'OBJECT',virtuals,names,ds) spec.projid = self->get_row_value(row,'PROJID',virtuals,names,ds) spec.backend = self->get_row_value(row,'BACKEND',virtuals,names,ds) spec.observer = self->get_row_value(row,'OBSERVER',virtuals,names,ds) spec.telescope = self->get_row_value(row,'TELESCOP',virtuals,names,ds) spec.bandwidth = self->get_row_value(row,'BANDWID',virtuals,names,dd) date_obs = self->get_row_value(row,'DATE_OBS',virtuals,names,ds) fd = fitsdateparse(date_obs) spec.date = strmid(date_obs,0,10) spec.utc = (fd[3]*60.0+fd[4])*60.0 + fd[5] juldate,fd,mjd ; juldate returnes Reduced Julian Date (RJD): MJD = RJD - 0.5 spec.mjd=mjd - 0.5 spec.timestamp = strtrim(self->get_row_value(row,'TIMESTAMP',virtuals,names,ds),2) spec.exposure = self->get_row_value(row,'EXPOSURE',virtuals,names,dd) spec.duration = self->get_row_value(row,'DURATION',virtuals,names,dd) spec.tambient = self->get_row_value(row,'TAMBIENT',virtuals,names,!values.f_nan) spec.pressure = self->get_row_value(row,'PRESSURE',virtuals,names,!values.f_nan) * 133.322368d ; mm Hg -> Pa spec.humidity = self->get_row_value(row,'HUMIDITY',virtuals,names,!values.f_nan) spec.tsys = self->get_row_value(row,'TSYS',virtuals,names,dd) spec.tsysref = self->get_row_value(row,'TSYSREF',virtuals,names,dd) sitelong = self->get_row_value(row,'SITELONG',virtuals,names,dd) sitelat = self->get_row_value(row,'SITELAT',virtuals,names,dd) siteelev = self->get_row_value(row,'SITEELEV',virtuals,names,dd) ; if this is the GBT, and it's got a positive longitude, its sdfits ver 1.1; ; turn it negative (bug in SDFITS 1.1); this is fixed in ver 1.2 if (spec.telescope eq "NRAO_GBT") and (sitelong gt 0.0) then sitelong = -sitelong spec.site_location = [sitelong, sitelat, siteelev] ; assume row has a data tag name! *spec.data_ptr = row.data spec.units = self->get_row_value(row,'TUNIT7',virtuals,names,"") spec.frequency_type = self->format_sdfits_freq_type(self->get_row_value(row,'CTYPE1',virtuals,names,ds)) spec.reference_frequency = self->get_row_value(row,'CRVAL1',virtuals,names,dd) ; ref channel 1-based in sdfits, 0-based in idl spec.reference_channel = self->get_row_value(row,'CRPIX1',virtuals,names,1.0) - 1.0 spec.frequency_interval = self->get_row_value(row,'CDELT1',virtuals,names,dd) ; frequency_resolution should always be positive spec.frequency_resolution = abs(self->get_row_value(row,'FREQRES',virtuals,names,spec.frequency_interval)) ctype2 = self->get_row_value(row,'CTYPE2',virtuals,names,ds) ctype3 = self->get_row_value(row,'CTYPE3',virtuals,names,ds) spec.coordinate_mode = self->coord_mode_from_types(ctype2,ctype3) spec.equinox = self->get_row_value(row,'EQUINOX',virtuals,names,2000.0d) spec.longitude_axis = self->get_row_value(row,'CRVAL2',virtuals,names,dd) spec.latitude_axis = self->get_row_value(row,'CRVAL3',virtuals,names,dd) spec.equinox = self->get_row_value(row,'EQUINOX',virtuals,names,2000.0d) spec.radesys = self->get_row_value(row,'RADESYS',virtuals,names,'') if (spec.radesys eq '' and spec.coordinate_mode eq 'RADEC') then begin if (spec.equinox eq 2000.0) then begin spec.radesys = 'FK5' endif else begin if (spec.equinox eq 1950.0) then begin spec.radesys = 'FK4' endif else begin spec.radesys = 'GAPPT' endelse endelse endif spec.target_longitude = self->get_row_value(row,'TRGTLONG',virtuals,names,0.0D) spec.target_latitude = self->get_row_value(row,'TRGTLAT',virtuals,names,0.0D) crval4 = self->get_row_value(row,'CRVAL4',virtuals,names,0) spec.polarization = self->format_sdfits_polarization(crval4[0]) spec.scan_number = self->get_row_value(row,'SCAN',virtuals,names,di) obsmode = self->get_row_value(row,'OBSMODE',virtuals,names,ds) self->parse_sdfits_obsmode,obsmode,proc,swstate,swtchsig spec.procedure = proc spec.switch_state = swstate spec.switch_sig = swtchsig spec.obsid = self->get_row_value(row,'OBSID',virtuals,names,ds) spec.frontend = self->get_row_value(row,'FRONTEND',virtuals,names,ds) ; FEEDXOFF/FEEDEFF vs older BEAMXOFF/BEAMEOFF ; current - use default of nan to watch for missing case spec.feedxoff = self->get_row_value(row,'FEEDXOFF',virtuals,names,nan) if not finite(spec.feedxoff) then begin ; not found, try older version, standard defaults spec.feedxoff = self->get_row_value(row,'BEAMXOFF',virtuals,names,dd) spec.feedeoff = self->get_row_value(row,'BEAMEOFF',virtuals,names,dd) endif else begin ; feedeoff should be there, standard defaults spec.feedeoff = self->get_row_value(row,'FEEDEOFF',virtuals,names,dd) endelse spec.mean_tcal = self->get_row_value(row,'TCAL',virtuals,names,dd) spec.velocity_definition = self->get_row_value(row,'VELDEF',virtuals,names,ds) spec.frame_velocity = self->get_row_value(row,'VFRAME',virtuals,names,dd) spec.observed_frequency = self->get_row_value(row,'OBSFREQ',virtuals,names,dd) spec.lst = self->get_row_value(row,'LST',virtuals,names,dd) spec.azimuth = self->get_row_value(row,'AZIMUTH',virtuals,names,dd) spec.elevation = self->get_row_value(row,'ELEVATIO',virtuals,names,dd) spec.subref_state = self->get_row_value(row,'SUBREF_STATE',virtuals,names,1) spec.line_rest_frequency = self->get_row_value(row,'RESTFREQ',virtuals,names,dd) spec.center_frequency = self.index->get_center_frequency(spec.reference_frequency,spec.reference_channel,spec.frequency_interval,n_elements(row.data)) spec.sampler_name = self->get_row_value(row,'SAMPLER',virtuals,names,ds) spec.feed = self->get_row_value(row,'FEED',virtuals,names,di) spec.srfeed = self->get_row_value(row,'SRFEED',virtuals,names,di) spec.sideband = self->get_row_value(row,'SIDEBAND',virtuals,names,ds) spec.procseqn = self->get_row_value(row,'PROCSEQN',virtuals,names,0) spec.procsize = self->get_row_value(row,'PROCSIZE',virtuals,names,di) spec.source_velocity = self->get_row_value(row,'VELOCITY',virtuals,names,dd) spec.zero_channel = self->get_row_value(row,'ZEROCHAN',virtuals,names,nan) spec.freq_switch_offset = self->get_row_value(row,'FOFFREF1',virtuals,names,dd) spec.sig_state = self->translate_sig(self->get_row_value(row,'SIG',virtuals,names,'T')) spec.cal_state = self->translate_cal(self->get_row_value(row,'CAL',virtuals,names,'T')) spec.caltype = self->get_row_value(row,'CALTYPE',virtuals,names,'') ; apply feed offsets for old data when non-zero if apply_offsets then begin dcdofeedoffsets,spec endif ; additional stuff - all from index file spec.integration = integration spec.if_number = if_number spec.feed_num = feed_num spec.polarization_num = pol_num spec.nsave = nsave ptr_free,names.row ptr_free,names.missing ptr_free,names.virtuals return, spec END ;+ ; Frees the memory referenced by pointers in the passed in structure ; @param row_info {in}{type=struct} contains pointers to row number lists, integration and if number lists ; @private ;- PRO IO_SDFITS_LINE::free_group_row_info, row_info compile_opt idl2 for i=0, (n_elements(row_info)-1) do begin if ptr_valid(row_info[i].rows) then ptr_free, row_info[i].rows if ptr_valid(row_info[i].integrations) then ptr_free, row_info[i].integrations if ptr_valid(row_info[i].if_numbers) then ptr_free, row_info[i].if_numbers if ptr_valid(row_info[i].feed_nums) then ptr_free, row_info[i].feed_nums if ptr_valid(row_info[i].pol_nums) then ptr_free, row_info[i].pol_nums if ptr_valid(row_info[i].nsaves) then ptr_free, row_info[i].nsaves if ptr_valid(row_info[i].index) then ptr_free, row_info[i].index endfor END ;+ ; Creates and returns a line_sdfits object ; @param file_name {in}{type=string} full path file name for sdfits file of with spectral line data ; @private ;- FUNCTION IO_SDFITS_LINE::get_new_fits_obj, file_name, _EXTRA=ex compile_opt idl2 return, obj_new(self.sdfits_class_name,file_name,version=self.version,_EXTRA=ex) END ;+ ; ; Determines if any files have grown, and appends new rows to the index file ; ; @uses get_new_row_locations ; @uses group_row_locations ; ; @examples ; >io->set_file, 'filename' ; >io->list ; ; here you see contents of 'filename' ; >io->update ; even though 'filename' hasnt changed ; >'No sdfits file(s) in index need updating' ; ; now new rows are appended to 'filename' (by the online filler perhaps) ; >io->update ; >'Index file updated with 5 rows' ; >io->list ; ; here you see the original contents of 'filname' ; ; plus the extra 5 new rows. ; ; NOTE: the index file was NOT re-created from scatch ; ;- PRO IO_SDFITS_LINE::load_new_sdfits_rows if self.index->is_file_loaded() eq 0 then message, "Cannot update until an index file has been created/loaded" ; for every file in sdfits file, check that its 'nsync files = self.index->get_column_values('file',/unique) for i=0,n_elements(files)-1 do begin status = 0 locations = self->get_new_row_locations(files[i],status) if status then begin ; group the locations by filename-extension to save file I/O time location_groups = self->group_locations(locations) ; use a fits object for accessing the fits file that has our missing rows fits = self->get_fits(files[i]) for j=0,n_elements(location_groups)-1 do begin group = location_groups[j] new_rows = fits->get_and_eval_rows(missing, virtuals, (*group.rows), ext=group.ext) ; watch for bad return value - empty extension if size(new_rows,/type) ne 8 then continue project = fits->get_extension_header_value('PROJID') file_name = fits->get_file_name() startrow = (*group.rows)[0] self.index->update_file, new_rows, project, file_name, group.ext, missing, virtuals, startrow ; free memory ptr_free, group.rows endfor ; for each extension endif else begin ; if new rows found print, "No sdfits file(s) in index need updating" endelse endfor ; for each file ; get the new additions into memory self.index->read_file self.index_synced = 1 END ;+ ; Like other group functions in I/O, groups an array of structures by their filename, ; and extension tags. This is used so that rows in the same file-extension can be ; accessed all at once. ; @param locations {in}{required}{type=array} array of structures containing filenames, ext, and row # ; @returns array of structures, each structure giving a filename, extension, and array of row nubmers ; @private ;- FUNCTION IO_SDFITS_LINE::group_locations, locations compile_opt idl2 group = {location_group,filename:string(replicate(32B,256)),ext:0L,rows:ptr_new()} ; get all files files = locations.filename unique_files = files[uniq(files,sort(files))] for i = 0, (n_elements(unique_files)-1) do begin file_locals = locations[ where(locations.filename eq unique_files[i]) ] exts = file_locals.ext unique_exts = exts[uniq(exts,sort(exts))] for j = 0, (n_elements(unique_exts)-1) do begin group = {location_group} group.rows = ptr_new(/allocate_heap) file_ext_locals = file_locals[ where(file_locals.ext eq unique_exts[j]) ] ; collapse the array into one struct group.filename = file_ext_locals[0].filename group.ext = file_ext_locals[0].ext *group.rows = file_ext_locals.row if (i eq 0) and (j eq 0) then groups = [group] else groups = [groups,group] endfor endfor return, groups END ;+ ; Compares index file with sdfits files listed in it, and looks for new rows in the sdfits file. ; @returns array of structures, each giving location of file, extension, and row # of new sdfits row ; @private ;- FUNCTION IO_SDFITS_LINE::get_new_row_locations, filename, status compile_opt idl2, hidden loc = {row_location, filename:string(replicate(32B,256)), ext:0L, row:0L} ; get the fits object for this file fits = self->get_fits(filename) fits->update_properties ; retrieve the extensions in the file file_num_exts = fits->get_number_extensions() file_exts = lindgen(file_num_exts)+1 ; collect all index rows for this file index_rows = self.index->search_for_row_info(file=filename) ; collect all the extensions in the index file index_exts = index_rows.extension index_exts = index_exts[uniq(index_exts,sort(index_exts))] ; what extensions does the index not have? for i=0,n_elements(file_exts)-1 do begin ; dont just compare the number of extesions, be more thourogh count = 0 ind = where(file_exts[i] eq index_exts, count) if count eq 0 then begin if n_elements(missing_ext) eq 0 then missing_ext = [file_exts[i]] else missing_ext = [missing_ext, file_exts[i]] endif endfor ; for each extension in file ; recored the locations of all rows in the missing extensions if n_elements(missing_ext) ne 0 then begin for i=0,n_elements(missing_ext)-1 do begin ; get number of rows in this fits file - row numbers are 0-based num_rows = fits->get_ext_num_rows(missing_ext[i]) for j=0,num_rows-1 do begin loc = {row_location} loc.filename = filename loc.ext = missing_ext[i] loc.row = j if n_elements(locs) eq 0 then locs=[loc] else locs=[locs,loc] endfor ; for each missing row number endfor ; for each missing extension endif ; missing rows in index extensions? for i=0,n_elements(index_exts)-1 do begin ; for each extension, compare row's. get the index's ext's rows index_rows = self.index->search_for_row_info(file=filename,ext=index_exts[i]) index_row_nums = index_rows.row_num ; get the fits files rows for this extension file_ext_num_rows = fits->get_ext_num_rows(index_exts[i]) ; although there aren't 'row numbers' in the sdfits files, treat them ; like they are, and don't just compare number of rows missing_rows = [-1] for j=0,file_ext_num_rows-1 do begin count = 0 ind = where(j eq index_row_nums,count) if count eq 0 then begin ; build the list of missing rows for this ext missing_rows = [missing_rows, j] endif endfor ; for each file row number for this extension ; if rows are missing, record their location in the sdfits file if n_elements(missing_rows) gt 1 then begin missing_rows = missing_rows[1:n_elements(missing_rows)-1] for j=0,n_elements(missing_rows)-1 do begin loc = {row_location} loc.filename = filename loc.ext = index_exts[i] loc.row = missing_rows[j] if n_elements(locs) eq 0 then locs=[loc] else locs=[locs,loc] endfor endif ; if rows missing endfor ; for each ext in index if n_elements(locs) eq 0 then begin status = 0 return, -1 endif else begin status = 1 return, locs endelse END ;+ ; Finds the latest files in the online directory (reads status file ; produced by online sdfits). ; @param newest_acs {out}{optional}{type=string} the newest ; spectrometer fits file in the online directory ; @param newest_dcr {out}{optional}{type=string} the newest dcr fits ; file in the online directory ; @param newest_sp {out}{optional}{type=string} the newest spectral ; processor fits file in the online directory ; @param newest_zpec {out}{optional}{type=string} the newest ; zpectrometer fits file in the online directory ; @param status {out}{optional}{type=boolean} 0 - error, 1 - success ; @returns the newest spectral line fits file (ignoring any dcr and ; zpec fits files) in the online directory ;- FUNCTION IO_SDFITS_LINE::get_online_files, newest_acs, newest_dcr, newest_sp, newest_zpec, status compile_opt idl2 status = 0 if obj_valid(self.online_status) eq 0 then begin ; check if /home/sdfits is visible if file_test(self.online_dir) eq 0 then begin message, "Cannot find online files: "+self.online_dir+" not visible", /info return, -1 endif self.online_status = obj_new('online_status_file',self.online_dir) endif if self.online_status->get_status() eq 0 then return, -1 newest = self.online_status->get_filenames(newest_acs, newest_dcr, newest_sp, newest_zpec, status) ; warning message if absolutely nothing found if strlen(newest_acs+newest_dcr+newest_sp+newest_zpec) eq 0 then begin message, "No fits files shown in online sdfits status file in : "+self.online_dir, /info return, -1 endif return, newest END ;+ ; Finds the latest online info structs for all of the lines in the ; current online sdfits info file. ; @param newest_acs {out}{optional}{type=online_status_info_strct} the current ; spectrometer fits file info structure in the online directory ; @param newest_dcr {out}{optional}{type=online_status_info_strct} the current ; dcr fits file info structure in the online directory ; @param newest_sp {out}{optional}{type=online_status_info_strct} the current ; spectral processor fits file info structure in the online directory ; @param newest_zpec {out}{optional}{type=online_status_info_strct} the current ; zpectrometer fits file info structure in the online directory ; @param status {out}{optional}{type=boolean} 0 - error, 1 - success ; @returns the newest spectral line fits file info structure (ignoring any dcr and ; zpec fits files) in the online directory ;- FUNCTION IO_SDFITS_LINE::get_online_infos, newest_acs, newest_dcr, newest_sp, newest_zpec, status compile_opt idl2 status = 0 if obj_valid(self.online_status) eq 0 then begin ; check if /home/sdfits is visible if file_test(self.online_dir) eq 0 then begin message, "Cannot find online files: "+self.online_dir+" not visible", /info return, -1 endif self.online_status = obj_new('online_status_file',self.online_dir) endif if self.online_status->get_status() eq 0 then return, -1 newest = self.online_status->get_all_infos(newest_acs, newest_dcr, newest_sp, newest_zpec, status) ; warning message if absolutely nothing found if strlen(newest_acs.file+newest_dcr.file+newest_sp.file+newest_zpec.file) eq 0 then begin message, "No fits files shown in online sdfits status file in : "+self.online_dir, /info return, -1 endif return, newest END ;+ ; Connects to a file in the online directory, and sets up object so that ; every time a query of the index file is done, the update method is called. ; This depends on another process(es) that should be updating the sdfits and ; index files for the current project. ; ; @param file_name {in}{required}{type=string} base or full filename to connect to ; @keyword test {in}{optional}{type=bool} if true, this is a test, and the online directory does not need to be visible ; ;- PRO IO_SDFITS_LINE::set_online, file_name, test=test compile_opt idl2 ; find this in the online_status if obj_valid(self.online_status) eq 0 then begin ; check if /home/sdfits is visible if file_test(self.online_dir) eq 0 and keyword_set(test) eq 0 then begin message, "Cannot find online files: "+self.online_dir+" not visible", /info return endif self.online_status = obj_new('online_status_file',self.online_dir) endif if self.online_status->get_status() eq 0 then begin message,"There was a problem reading the online status file. No online files are available.",/info return endif ; this must be either ACS, SP, or ZPEC self.online_info = self.online_status->get_status_info('acs') if self.online_info.file ne file_name then begin self.online_info = self.online_status->get_status_info('sp') if self.online_info.file ne file_name then begin self.online_info = self.online_status->get_status_info('zpec') if self.online_info.file ne file_name then begin message,"The requested file is not currently an online file.",/info message,"Use 'offline' to connect to this file:" + file_name,/info return endif endif endif ; check if path is included or not file_path = file_dirname(file_name) file_name = file_basename(file_name) if file_path eq "." then file_path = self.online_dir ; if keyword_set(test) then self.online_lock_dir = file_path ; construct the name of the index we should load parts=strsplit(file_name,'.',/extract) index_file = strjoin(parts[0:n_elements(parts)-2],'.')+'.index' ; for online mode, the daemons really should create it; ; therefore, if this index doesn't exist, raise an error if file_test(file_path+'/'+index_file) eq 0 then begin message, "Cannot use online mode: index file not created yet. Wait until first scan is finished or seek help.", /info return endif ; discard all other fits objects self->free_fits_objs ; set the names of the index file, and where it's located self->set_file_path, file_path self->set_index_file_name, index_file ; mark the state as online self.online = 1 ; add this file, the index should get loaded ; check the status of the lock file ; self->check_lock_file self->add_file, file_name, max_nrows=self.online_info.index+1 ; mark this io object as dedicated to one file self.one_file = 1 END ;+ ; Reads the new lines in an index file into memory, if the size of the ; index file has changed. Uses a lock file since other processes might ; be reading/writing the index file. ; @uses INDEX_FILE::read_new_rows ;- PRO IO_SDFITS_LINE::update compile_opt idl2 if self.online then begin ; monitor online status info curr_info = self.online_status->get_status_info(self.online_info.backend) if curr_info.file ne self.online_info.file then begin ; no longer the current online file ; turn off online mode self.online=0 ; recursively invoke update to get the standard update ; to make sure the copy in memory here is fully up to date self->update ; and now tell the user about all of this message,'This data file is no longer the current online file for this backend.',/info message,'Automatic updates on this file are now turned off in GBTIDL.',/info message,'Use "online" to switch to the current online file.',/info endif else begin if curr_info.index ne self.online_info.index then begin ; need to update, index has changed self.index->read_new_rows, num_new_lines, max_nrows=(curr_info.index+1) self.online_info = curr_info endif endelse endif else begin ; otherwise need to look at the index file size to watch for changes current_fi = file_info(self.index->get_full_file_name()) old_fi = self.index->get_info() if old_fi.size ne current_fi.size then begin ; the size has changed self.index->set_info, current_fi ; check the status of the lock file ; The lock file check was commented out by JB, as discussed with gbtidl team. ; It's only purpose is to print a warning message on the screen. ; self->check_lock_file self.index->read_new_rows, num_new_lines endif ;else begin ; print, "Index file has not changed." ;endelse endelse END ;+ ; Check on the existance of a lock file. If it exists, then this ; likely means that the on-line filler is currently writing to this ; file. In that case, a message is printed out informing the user ; that the online information may not be up to date. ;- ;PRO IO_SDFITS_LINE::check_lock_file ; compile_opt idl2 ; ; lockfile = self.online_lock_dir + '/' + self.online_proj + '.lock' ; if the file exists, we have to wait for the owner to delete it ; if file_test(lockfile) then begin ; print, "The online filler appears to be writing to the online data file." ; print, "The available online data in this IDL session may not be up to date." ; endif ;END FUNCTION IO_SDFITS_LINE::get_index_class_name compile_opt idl2 return, self.index_class_name END