Source code for sourcefinding.runbdsf

"""This module contains all the functionality to run a FITS image
through the LOFAR source finding software PyBDSF 
(https://github.com/lofar-astron/PyBDSF). The class
``BDSFImage()`` creates an object whose attributes can be any number
of PyBDSF parameters. Source finding is performed by calling the
object method ``find_sources()``, which calls the PyBDSF function
``process_image()`` and returns the output object.

"""
import warnings
import logging
from datetime import datetime
import numpy as np
import bdsf
from database.dbclasses import Image
from timeout import timeout


# create logger
sf_logger = logging.getLogger('vdp.sourcefinding.runbdsf')


[docs]def write_sources(out): """Calls PyBDSF functions ``write_catalog()`` and ``export_image()`` to record results from source finding on an image. Parameters ---------- out : ``bdsf.image.Image`` instance The object output by PyBDSF after running its source finding task ``process_image()``. """ # Write the source list catalog, ascii and ds9 regions file out.write_catalog(format='ds9', catalog_type='srl', clobber=True)
#out.write_catalog(format='ascii', catalog_type='srl', clobber=True) # Write the residual image #out.export_image(img_type='gaus_resid', clobber=True) # Write the model image #out.export_image(img_type='gaus_model', clobber=True)
[docs]class BDSFImage(Image): """Object to be manipulated and read into PyBDSF. Inherits all methods defined for Image class, but overrides initialization. The number of attributes and their values will change based on what the user specifies in the run configuration file. """ def __init__(self, image, **kwargs): """Initializes the Image subclass object. PyBDSF will use any arguments it recognizes and ignore the rest.""" self.filename = image # These will be overwritten if in config file self.quiet = True self.box_incr = 20 self.max_iter = 5 # Set our own default rms_box parameter self.set_rms_box() # Set attributes from config file for key, value in kwargs.items(): if key == 'rms_box': if value == '' or value == 'None': value = None if key == 'rms_box_bright': if value == '' or value == 'None': value = None if key == 'adaptive_thresh': if value == '' or value == 'None': value = None setattr(self, key, value)
[docs] def set_rms_box(self): """Sets the PyBDSF ``rms_box`` parameter to a box size 1/10th of the image size and a step size one third of the box size. This "VLITE default" ``rms_box`` yields slightly better results with fewer artifacts than if left as a free parameter for PyBDSF to calculate. A custom ``rms_box`` can be defined in the configuratrion file which will supersede the one defined here. """ data, hdr = Image.read(self) box_size = int(round(hdr['NAXIS2'] / 10.)) step_size = int(round(box_size / 3.)) self.rms_box = (box_size, step_size) small_box_size = int(round(box_size / 5.)) small_step_size = int(round(step_size / 5.)) self.rms_box_bright = (small_box_size, small_step_size)
[docs] def get_attr(self): """Return all object attributes as a dictionary. This is fed into ``bdsf.process_image()`` as parameter arguments. """ return self.__dict__
# Function will timeout after 5 min
[docs] @timeout() def find_sources(self): """Run PyBDSF ``process_image()`` task using object attributes as parameter inputs. Returns ``None`` if PyBDSF fails. Wrapped in a timeout function so processing is killed if taking longer than 5 minutes. """ sf_logger.info('Extracting sources...') start = datetime.now() opts = self.get_attr() with warnings.catch_warnings(): warnings.filterwarnings('ignore', r'invalid value') try: out = bdsf.process_image(opts) except: out = None try: sf_logger.info(' -- found {} sources in {:.2f} seconds'.format( out.nsrc, (datetime.now() - start).total_seconds())) except AttributeError: try: sf_logger.info(' -- found {} islands in {:.2f} seconds'.format( out.nisl, (datetime.now() - start).total_seconds())) except AttributeError: sf_logger.info(' -- PyBDSF failed to process image.') return out
[docs] def minimize_islands(self): """Incrementally increases and decreases the ``bdsf.process_image()`` argument ``rms_box`` box size until a minimum number of islands are found. This technique works best on images with significant artifacts where false detections are the biggest concern. This also takes a really long time since it is running ``bdsf.process_image`` numerous times, so is probably only ever worth using when analyzing a small number of images. """ # Initial run sf_logger.info('Starting minimize_islands with box {}...'.format( self.rms_box)) self.stop_at = 'isl' # stop fitting at islands out = self.find_sources() if out is not None: box0 = out.rms_box # record initial box min_isl = out.nisl # initialize island number minimum else: # Only fails when user's box is too small box0 = self.rms_box min_isl = 99999 sf_logger.info('PyBDSF has failed with box {}.'.format(box0)) sf_logger.info('Increasing rms_box...') opt_box = box0 # initialize optimal box box_size = box0[0] # Begin increasing box size loop i = 0 while i < self.max_iter: # stop at max number of iterations box_size += self.box_incr box_step = int(box_size / 3.) self.rms_box = (box_size, box_step) sf_logger.info('Trying box {}...'.format(self.rms_box)) out = self.find_sources() if out is not None: if out.nisl < min_isl: # new winner, keep going min_isl = out.nisl opt_box = self.rms_box elif out.nisl == min_isl: # tie, keep going pass else: # number of islands increased, stop break else: # usually happens when box size is too small and "an # unphysical rms value was encountered", so keep going sf_logger.info('PyBDSF has failed with box {}.'.format( self.rms_box)) sf_logger.info('Increasing rms_box...') i += 1 # Begin decreasing box size loop i = 0 box_size = box0[0] # reset the box size back to original while i < self.max_iter: box_size -= self.box_incr box_step = int(box_size / 3.) self.rms_box = (box_size, box_step) if box_size > 0: # stop if box size goes to 0 or below sf_logger.info('Trying box {}...'.format(self.rms_box)) out = self.find_sources() else: break if out is not None: if out.nisl < min_isl: # new winner, keep going min_isl = out.nisl opt_box = self.rms_box elif out.nisl == min_isl: # tie, keep going pass else: # number of islands increased, stop break else: # usually happens when box size is too small and "an # unphysical rms value was encountered", so stop here sf_logger.info('PyBDSF has failed with box {}.'.format( self.rms_box)) sf_logger.info('Stopping here.') break i += 1 sf_logger.info('Found minimum of {} islands using box {}.'.format( min_isl, opt_box)) # Final complete run with best parameters sf_logger.info('Final run...') self.rms_box = opt_box self.stop_at = None opt_out = self.find_sources() return opt_out