;;; @(#)combine_catalogs.pro 1.4 ;;; Tool to edit several source catalogs that are stored as FITS binary tables. ;;; The catalogs must include columns named X and Y. ;;; If the edited catalogs have the same number of entries then sources are ;;; matched and the catalogs are saved in the same order. ;;; Example: ;combine_catalogs,['central_1.sources','iarray_2.sources','fullfield_4.sources'] ;========================================================================== ;;; Clean up after the widget. ;========================================================================== PRO CombineCatalogsCleanup, top_base widget_control, top_base, GET_UVALUE=st ;; Free the heap vars allocated locally. ptr_free, (*st).catalogs, (*st).xpos, (*st).ypos,$ (*st).cat_num, (*st).entry_num, (*st).entry_ok ptr_free, st return end ;========================================================================== ;;; Routine to update the widget's appearance ;========================================================================== PRO RedrawCombineCatalogs, st widget_control, /HOURGLASS num_cats = n_elements((*st).catalog_names) psyms = [1,4,5,6,7] & num_syms = n_elements(psyms) for ii=0,num_cats-1 do begin name = (*st).catalog_names[ii] index = where( (*(*st).cat_num EQ ii) AND (*(*st).entry_ok EQ 1), count ) if (count GT 0) then begin dataset_2d, (*st).dataset_2d, (*(*st).xpos)[index], (*(*st).ypos)[index], $ DATASET_NAME=name, REDRAW=0, /UNITY_ASPECT,$ COLOR='white', PSYM=psyms[ii mod num_syms] good_name = name endif else dataset_2d, (*st).dataset_2d, DATASET_NAME=name, /DELETE, REDRAW=0 name = (*st).catalog_names[ii]+', removed' index = where( (*(*st).cat_num EQ ii) AND (*(*st).entry_ok EQ 0), count ) if (count GT 0) then begin dataset_2d, (*st).dataset_2d, (*(*st).xpos)[index], (*(*st).ypos)[index], $ DATASET_NAME=name, REDRAW=0, $ COLOR='red', PSYM=psyms[ii mod num_syms] good_name = name endif else dataset_2d, (*st).dataset_2d, DATASET_NAME=name, /DELETE, REDRAW=0 endfor dataset_2d, (*st).dataset_2d, DATASET_NAME=good_name, /REDRAW return end ;========================================================================== ;;; Event Handler Function ;========================================================================== FUNCTION CombineCatalogsEventFn, Event widget_control, /HOURGLASS new_event = 0 DestroyFlag = 0 SaveCatalogs= 0 ;; Get the state structure. top_base = Event.handler widget_control, top_base, GET_UVALUE=st ;; Process the event. case Event.ID of ;-------------------------------------------------------------------------- ; Add/Remove Source events. (*st).add_remove_button: $ begin ;; Get a mouse click and find nearest source. prompt='Middle-click on a source, or right-click to stop ' plot_window, (*st).pw_id, GET_MOUSE=point, PROMPT=prompt while (point[2] NE 4) do begin dum = min((point[0]-(*(*st).xpos))^2 + (point[1]-(*(*st).ypos))^2, imin) (*(*st).entry_ok)[imin] = ((*(*st).entry_ok)[imin] + 1) mod 2 RedrawCombineCatalogs, st tvcrs, (*(*st).xpos)[imin], (*(*st).ypos)[imin], /DATA plot_window, (*st).pw_id, GET_MOUSE=point, PROMPT=prompt endwhile end ;-------------------------------------------------------------------------- ; Print Source events. (*st).print_button: $ begin tnums=[0,1,4,5,8,9,11,13,17,19,20] ;; Get a mouse click and find nearest source. prompt='Middle-click on a source, or right-click to stop ' plot_window, (*st).pw_id, GET_MOUSE=point, PROMPT=prompt while (point[2] NE 4) do begin dum = min((point[0]-(*(*st).xpos))^2 + (point[1]-(*(*st).ypos))^2, imin) cat_num = (*(*st).cat_num) [imin] entry_num = (*(*st).entry_num)[imin] print_struct, (*(*st).catalogs[cat_num])[entry_num], TNUMS=tnums, $ FORM=['g','8','5'] tvcrs, (*(*st).xpos)[imin], (*(*st).ypos)[imin], /DATA plot_window, (*st).pw_id, GET_MOUSE=point, PROMPT=prompt endwhile end ;-------------------------------------------------------------------------- ; Save catalogs. (*st).save_button: SaveCatalogs = 1 ;-------------------------------------------------------------------------- ; Exit events. (*st).done_button: DestroyFlag = 1 else: print, 'unknown event in CombineCatalogsEventFn' endcase if SaveCatalogs then begin ;; Extract all catalogs that have good entries. num_cats = n_elements((*st).catalog_names) num_good = lonarr(num_cats) catalogs = ptrarr(num_cats) for ii=0,num_cats-1 do begin index = where( (*(*st).cat_num EQ ii) AND (*(*st).entry_ok EQ 1), count ) num_good[ii] = count if (count GT 0) then begin entry_num = (*(*st).entry_num)[index] catalogs[ii] = ptr_new( (*(*st).catalogs[ii])[entry_num] ) endif endfor index = where(num_good GT 0, num_cats) if (num_cats EQ 0) then print, 'No good entries any any catalog!' $ else begin num_good = num_good[index] catalogs = catalogs[index] catalog_names = (*st).catalog_names[index] xoffsets = (*st).xoffsets[index] yoffsets = (*st).yoffsets[index] ;; If all trimmed catalogs are the same length then sort them so their ;; entries match. if ( min(num_good EQ num_good[0]) EQ 1 ) then begin print, 'Sorting catalogs to match entries before saving.' xref = (*catalogs[0]).x + xoffsets[0] yref = (*catalogs[0]).y + yoffsets[0] num_entries = num_good[0] for ii=1,num_cats-1 do begin max_dist = 0.0 catalog = catalogs[ii] for num_sorted = 0,num_entries-2 do begin ; Extract the x,y values for the unsorted tail of the catalog. x = ((*catalog).x)[num_sorted:*] + xoffsets[ii] y = ((*catalog).y)[num_sorted:*] + yoffsets[ii] ; Find the entry that is closest to the current ref entry. temp = min((x - xref[num_sorted])^2 + (y - yref[num_sorted])^2, imin) max_dist = max_dist > sqrt(temp) ; Change imin from an index into the tail to an index into the ; catalog imin = imin + num_sorted ; Exchange that matching entry with the first entry in the unsorted ; tail of the catalog, which lengthens the sorted head by one and ; shortens the unsorted tail by one. matching_entry = (*catalog)[imin] (*catalog)[imin] = (*catalog)[num_sorted] (*catalog)[num_sorted] = matching_entry endfor ;num_sorted loop print, 'max matching distance:', max_dist endfor; catalog loop endif ; match catalogs ;; Save the catalogs to disk with the extension '.trimmed' for ii=0,num_cats-1 do begin cat_name = catalog_names[ii] trim_name = cat_name+'.trimmed' hdr = headfits( cat_name ) writefits, trim_name, '', hdr hdr = headfits( cat_name, EXTEN=1 ) sxaddpar, hdr, 'HISTORY', 'trimmed by combine_catalogs.pro, Version 1.4' mwrfits, (*catalogs[ii]), trim_name, hdr print, num_good[ii], trim_name, F='("Wrote ",I0," sources to ",A)' endfor endelse ; good entries found endif if DestroyFlag then widget_control, (*st).parent, /DESTROY return, new_event end ;========================================================================== PRO combine_catalogs, catalog_names, XOFFSETS=xoffsets, YOFFSETS=yoffsets,$ REMOVE_ALL=remove_all, WIDGET_TITLE=widget_title if (0 EQ n_elements(widget_title)) then widget_title = 'Combine Catalogs' entry_status = (keyword_set(remove_all) EQ 0) ;; Read the catalogs. num_cats = n_elements(catalog_names) catalogs = ptrarr(num_cats) num_entries = lonarr(num_cats) if (num_cats NE n_elements(xoffsets)) then xoffsets = fltarr(num_cats) if (num_cats NE n_elements(yoffsets)) then yoffsets = fltarr(num_cats) for ii=0,num_cats-1 do begin catalogs[ii] = ptr_new( mrdfits(catalog_names[ii],1) ) num_entries[ii] = n_elements( *catalogs[ii] ) endfor ;; Build vectors we'll routinely need during session. total_entries = total(num_entries) xpos = fltarr(total_entries) ypos = fltarr(total_entries) cat_num = intarr(total_entries) entry_num= lonarr(total_entries) entry_ok = replicate(entry_status,total_entries) jj = 0L for ii=0,num_cats-1 do begin xpos [jj] = (*catalogs[ii]).x + xoffsets[ii] ypos [jj] = (*catalogs[ii]).y + yoffsets[ii] cat_num [jj] = replicate(ii,num_entries[ii]) entry_num[jj] = lindgen(num_entries[ii]) jj = jj + num_entries[ii] endfor ;; Build the widget. center = ScreenCenter() xoffset = center(0) - 400 yoffset = center(1) - 250 parent = plot_window_topbase(TITLE=widget_title, XOFF=xoffset, YOFF=yoffset) top_base = widget_base( parent, /BASE_ALIGN_CENTER, $ EVENT_FUNC='CombineCatalogsEventFn', $ KILL_NOTIFY='CombineCatalogsCleanup', $ /COLUMN, /SPACE, XPAD=0, YPAD=0 ) upper_base = widget_base( top_base, /ALIGN_LEFT, /ROW, $ SPACE=8, XPAD=0, YPAD=0 ) add_remove_button = widget_button( upper_base, VALUE='Add/Remove Sources' ) print_button = widget_button( upper_base, VALUE='Print Sources' ) save_button = widget_button( upper_base, VALUE='Save Trimmed Catalogs' ) done_button = widget_button( upper_base, VALUE='Exit' ) dataset_2d, dataset_2d, PARENT_WIDGET=top_base ;; Dive into the bowels of the dataset_2d object and grab the plot_window id. widget_control, dataset_2d, GET_UVALUE=st ;; Build state structure. state={ parent:parent, $ add_remove_button:add_remove_button, print_button:print_button, $ save_button:save_button, done_button:done_button, $ dataset_2d:dataset_2d, pw_id:(*st).pw_id, $ catalog_names:catalog_names, catalogs:catalogs, $ num_entries:num_entries, $ xpos: ptr_new(xpos), ypos: ptr_new(ypos), $ cat_num: ptr_new(cat_num), $ entry_num:ptr_new(entry_num), entry_ok: ptr_new(entry_ok), $ xoffsets:xoffsets, yoffsets:yoffsets $ } ;; Save state structure. widget_control, top_base, SET_UVALUE=ptr_new(state, /NO_COPY) widget_control, parent, /REALIZE xmanager, 'CombineCatalogs', parent, EVENT_HANDLER='PlotWindowTopbaseEventFn', /NO_BLOCK widget_control, top_base, GET_UVALUE=st RedrawCombineCatalogs, st return end