Beispiel #1
0
    def _localize(self, name, **kwargs):

        nstep = kwargs.get('nstep')
        dtheta_max = kwargs.get('dtheta_max')
        update = kwargs.get('update', True)
        prefix = kwargs.get('prefix', '')
        use_cache = kwargs.get('use_cache', False)
        free_background = kwargs.get('free_background', False)
        free_radius = kwargs.get('free_radius', None)
        fix_shape = kwargs.get('fix_shape', False)

        saved_state = LikelihoodState(self.like)
        loglike_init = -self.like()
        self.logger.debug('Initial Model Log-Likelihood: %f', loglike_init)

        if not free_background:
            self.free_sources(free=False, loglevel=logging.DEBUG)

        if free_radius is not None:
            diff_sources = [s.name for s in self.roi.sources if s.diffuse]
            skydir = self.roi[name].skydir
            free_srcs = [s.name for s in
                         self.roi.get_sources(skydir=skydir,
                                              distance=free_radius,
                                              exclude=diff_sources)]
            self.free_sources_by_name(free_srcs, pars='norm',
                                      loglevel=logging.DEBUG)

        src = self.roi.copy_source(name)
        skydir = src.skydir
        skywcs = self.geom.wcs
        src_pix = skydir.to_pixel(skywcs)

        fit0 = self._fit_position_tsmap(name, prefix=prefix,
                                        dtheta_max=dtheta_max,
                                        zmin=-3.0,
                                        use_pylike=False)

        self.logger.debug('Completed localization with TS Map.\n'
                          '(ra,dec) = (%10.4f,%10.4f) '
                          '(glon,glat) = (%10.4f,%10.4f)',
                          fit0['ra'], fit0['dec'],
                          fit0['glon'], fit0['glat'])

        # Fit baseline (point-source) model
        self.free_source(name, loglevel=logging.DEBUG)
        if fix_shape:
            self.free_source(name, free=False, pars='shape',
                             loglevel=logging.DEBUG)
        fit_output = self._fit(loglevel=logging.DEBUG, **
                               kwargs.get('optimizer', {}))

        # Save likelihood value for baseline fit
        loglike_base = fit_output['loglike']
        self.logger.debug('Baseline Model Log-Likelihood: %f', loglike_base)

        o = defaults.make_default_tuple(defaults.localize_output)
        o.name = name
        o.config = kwargs
        o.fit_success = True
        o.loglike_init = loglike_init
        o.loglike_base = loglike_base
        o.loglike_loc = np.nan
        o.dloglike_loc = np.nan

        if fit0['fit_success']:
            scan_cdelt = 2.0 * fit0['pos_r95'] / (nstep - 1.0)
        else:
            scan_cdelt = np.abs(skywcs.wcs.cdelt[0])

        self.logger.debug('Refining localization search to '
                          'region of width: %.4f deg',
                          scan_cdelt * nstep)

        fit1 = self._fit_position_scan(name,
                                       skydir=fit0['skydir'],
                                       scan_cdelt=scan_cdelt,
                                       **kwargs)

        o.loglike_loc = fit1['loglike']
        o.dloglike_loc = o.loglike_loc - o.loglike_base
        o.tsmap = fit0.pop('tsmap')
        o.tsmap_peak = fit1.pop('tsmap')
        # o.update(fit1)

        # Best fit position and uncertainty from fit to TS map
        o.fit_init = fit0

        # Best fit position and uncertainty from pylike scan
        o.fit_scan = fit1
        o.update(fit1)

        cdelt0 = np.abs(skywcs.wcs.cdelt[0])
        cdelt1 = np.abs(skywcs.wcs.cdelt[1])
        pix = fit1['skydir'].to_pixel(skywcs)
        o.pos_offset = skydir.separation(fit1['skydir']).deg
        o.xpix = float(pix[0])
        o.ypix = float(pix[1])
        o.deltax = (o.xpix - src_pix[0]) * cdelt0
        o.deltay = (o.ypix - src_pix[1]) * cdelt1

        o.ra_preloc = skydir.ra.deg
        o.dec_preloc = skydir.dec.deg
        o.glon_preloc = skydir.galactic.l.deg
        o.glat_preloc = skydir.galactic.b.deg

        if o.pos_offset > dtheta_max:
            o.fit_success = False

        if not o.fit_success:
            self.logger.warning('Fit to localization contour failed.')
        elif not o.fit_inbounds:
            self.logger.warning('Best-fit position outside of search region.')
        else:
            self.logger.info('Localization succeeded.')

        if update and ((not o.fit_success) or (not o.fit_inbounds)):
            self.logger.warning(
                'Localization failed.  Keeping existing position.')

        if update and o.fit_success and o.fit_inbounds:
            self.logger.info('Updating source %s '
                             'to localized position.', name)
            src = self.delete_source(name)
            src.set_position(fit1['skydir'])
            self.add_source(name, src, free=True)
            self.free_source(name, loglevel=logging.DEBUG)
            if fix_shape:
                self.free_source(name, free=False, pars='shape',
                                 loglevel=logging.DEBUG)

            fit_output = self.fit(loglevel=logging.DEBUG)
            o.loglike_loc = fit_output['loglike']
            o.dloglike_loc = o.loglike_loc - o.loglike_base
            src = self.roi.get_source_by_name(name)

            src['glon_err'] = o.glon_err
            src['glat_err'] = o.glat_err
            src['ra_err'] = o.glon_err
            src['dec_err'] = o.glat_err
            src['pos_err'] = o.pos_err
            src['pos_err_semimajor'] = o.pos_err_semimajor
            src['pos_err_semiminor'] = o.pos_err_semiminor
            src['pos_r68'] = o.pos_r68
            src['pos_r95'] = o.pos_r95
            src['pos_r99'] = o.pos_r99
            src['pos_angle'] = o.pos_angle
            src['pos_gal_cov'] = o.pos_gal_cov
            src['pos_gal_corr'] = o.pos_gal_corr
            src['pos_cel_cov'] = o.pos_cel_cov
            src['pos_cel_corr'] = o.pos_cel_corr
        else:
            saved_state.restore()
            self._sync_params(name)
            self._update_roi()

        self.logger.info('Localization completed with new position:\n'
                         '(  ra, dec) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                         '(glon,glat) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                         'offset = %8.4f r68 = %8.4f r95 = %8.4f r99 = %8.4f',
                         o.ra, o.ra_err, o.dec, o.dec_err,
                         o.glon, o.glon_err, o.glat, o.glat_err,
                         o.pos_offset, o.pos_r68, o.pos_r95, o.pos_r99)
        self.logger.info('LogLike: %12.3f DeltaLogLike: %12.3f',
                         o.loglike_loc, o.loglike_loc - o.loglike_init)

        return o
Beispiel #2
0
    def _extension(self, name, **kwargs):

        spatial_model = kwargs['spatial_model']
        width_min = kwargs['width_min']
        width_max = kwargs['width_max']
        width_nstep = kwargs['width_nstep']
        width = kwargs['width']
        free_background = kwargs['free_background']
        free_radius = kwargs.get('free_radius', None)
        fix_shape = kwargs.get('fix_shape', False)
        make_tsmap = kwargs.get('make_tsmap', False)
        tsmap_fitter = kwargs.get('tsmap_fitter', 'tsmap')
        update = kwargs['update']
        sqrt_ts_threshold = kwargs['sqrt_ts_threshold']

        if kwargs['psf_scale_fn']:

            def psf_scale_fn(t):
                return 1.0 + np.interp(np.log10(t), kwargs['psf_scale_fn'][0],
                                       kwargs['psf_scale_fn'][1])
        else:
            psf_scale_fn = None

        saved_state = LikelihoodState(self.like)
        loglike_init = -self.like()
        self.logger.debug('Initial Model Log-Likelihood: %f', loglike_init)

        if not free_background:
            self.free_sources(free=False, loglevel=logging.DEBUG)

        if free_radius is not None:
            diff_sources = [s.name for s in self.roi.sources if s.diffuse]
            skydir = self.roi[name].skydir
            free_srcs = [
                s.name for s in self.roi.get_sources(
                    skydir=skydir, distance=free_radius, exclude=diff_sources)
            ]
            self.free_sources_by_name(free_srcs,
                                      pars='norm',
                                      loglevel=logging.DEBUG)

        # Fit baseline model
        self.free_source(name, loglevel=logging.DEBUG)
        if fix_shape:
            self.free_source(name,
                             free=False,
                             pars='shape',
                             loglevel=logging.DEBUG)

        fit_output = self._fit(loglevel=logging.DEBUG, **kwargs['optimizer'])
        src = self.roi.copy_source(name)

        # Save likelihood value for baseline fit
        saved_state_base = LikelihoodState(self.like)
        loglike_base = fit_output['loglike']
        self.logger.debug('Baseline Model Log-Likelihood: %f', loglike_base)

        if not width:
            width = np.logspace(np.log10(width_min), np.log10(width_max),
                                width_nstep)

        width = np.array(width)
        width = width[width > 0]
        width = np.concatenate(([0.0], np.array(width)))

        o = defaults.make_default_tuple(defaults.extension_output)
        o.name = name
        o.width = width
        o.dloglike = np.zeros(len(width) + 1)
        o.loglike = np.zeros(len(width) + 1)
        o.loglike_base = loglike_base
        o.loglike_init = loglike_init
        o.config = kwargs
        o.ebin_ext = np.ones(self.enumbins) * np.nan
        o.ebin_ext_err = np.ones(self.enumbins) * np.nan
        o.ebin_ext_err_lo = np.ones(self.enumbins) * np.nan
        o.ebin_ext_err_hi = np.ones(self.enumbins) * np.nan
        o.ebin_ext_ul95 = np.ones(self.enumbins) * np.nan
        o.ebin_ts_ext = np.ones(self.enumbins) * np.nan
        o.ebin_loglike = np.ones((self.enumbins, len(width))) * np.nan
        o.ebin_dloglike = np.ones((self.enumbins, len(width))) * np.nan
        o.ebin_loglike_ptsrc = np.ones(self.enumbins) * np.nan
        o.ebin_loglike_ext = np.ones(self.enumbins) * np.nan
        o.ebin_e_min = self.energies[:-1]
        o.ebin_e_max = self.energies[1:]
        o.ebin_e_ctr = np.sqrt(o.ebin_e_min * o.ebin_e_max)

        self.logger.debug('Width scan vector:\n %s', width)

        if kwargs['fit_position']:
            ext_fit = self._fit_extension_full(name,
                                               spatial_model=spatial_model,
                                               optimizer=kwargs['optimizer'],
                                               width_max=width_max,
                                               width_min=width_min,
                                               width_nstep=width_nstep)
        else:
            ext_fit = self._fit_extension(name,
                                          spatial_model=spatial_model,
                                          optimizer=kwargs['optimizer'],
                                          psf_scale_fn=psf_scale_fn,
                                          width_max=width_max,
                                          width_min=width_min,
                                          width_nstep=width_nstep)

        o.update(ext_fit)

        # Fit with the best-fit extension model
        self.logger.info('Fitting extended-source model.')

        self.set_source_morphology(name,
                                   spatial_model=spatial_model,
                                   spatial_pars={
                                       'ra': o['ra'],
                                       'dec': o['dec'],
                                       'SpatialWidth': o['ext']
                                   },
                                   use_pylike=False,
                                   psf_scale_fn=psf_scale_fn)

        # Perform scan over width parameter
        o.loglike = self._scan_extension(name,
                                         spatial_model=spatial_model,
                                         width=width,
                                         optimizer=kwargs['optimizer'],
                                         psf_scale_fn=psf_scale_fn)

        self.set_source_morphology(name,
                                   spatial_model=spatial_model,
                                   spatial_pars={
                                       'ra': o['ra'],
                                       'dec': o['dec'],
                                       'SpatialWidth': o['ext']
                                   },
                                   use_pylike=False,
                                   psf_scale_fn=psf_scale_fn)

        fit_output = self._fit(loglevel=logging.DEBUG,
                               update=False,
                               **kwargs['optimizer'])

        o.source_fit = self.get_src_model(name,
                                          reoptimize=True,
                                          optimizer=kwargs['optimizer'])
        o.loglike_ext = fit_output['loglike']

        if kwargs['fit_ebin']:
            self._fit_extension_ebin(name,
                                     o,
                                     spatial_model=spatial_model,
                                     optimizer=kwargs['optimizer'],
                                     psf_scale_fn=psf_scale_fn,
                                     reoptimize=kwargs.get('reoptimize', True))

        if kwargs['save_model_map']:
            o.ext_tot_map = self.model_counts_map()
            o.ext_src_map = self.model_counts_map(name)
            o.ext_bkg_map = self.model_counts_map(exclude=[name])

        if make_tsmap:
            tsmap_model = {
                'SpatialModel': 'RadialDisk',
                'SpatialWidth': 0.1 * 0.8246211251235321
            }
            tsmap_model.update(src.spectral_pars)
            self.logger.info('Generating TS map.')
            if tsmap_fitter == 'tsmap':
                tsmap = self.tsmap(model=tsmap_model,
                                   map_skydir=SkyCoord(o['ra'],
                                                       o['dec'],
                                                       unit='deg'),
                                   map_size=max(1.0, 4.0 * o['ext']),
                                   exclude=[name],
                                   write_fits=False,
                                   write_npy=False,
                                   use_pylike=False,
                                   make_plots=False,
                                   loglevel=logging.DEBUG)
                o.tsmap = tsmap['ts']
            elif tsmap_fitter == 'tscube':
                tscube = self.tscube(model=tsmap_model,
                                     map_skydir=SkyCoord(o['ra'],
                                                         o['dec'],
                                                         unit='deg'),
                                     map_size=max(1.0, 4.0 * o['ext']),
                                     do_sed=False,
                                     write_fits=False,
                                     write_npy=False,
                                     make_plots=False,
                                     loglevel=logging.DEBUG)
                o.tsmap = tscube['ts']

        self.logger.info('Testing point-source model.')
        # Test point-source hypothesis
        self.set_source_morphology(name,
                                   spatial_model='PointSource',
                                   use_pylike=False,
                                   psf_scale_fn=psf_scale_fn)

        # Fit a point-source
        saved_state_base.restore()
        self.logger.debug('Fitting point-source model.')
        fit_output = self._fit(loglevel=logging.DEBUG, **kwargs['optimizer'])

        if src['SpatialModel'] == 'PointSource' and kwargs['fit_position']:
            loc = self.localize(name, update=False)
            o.loglike_ptsrc = loc['loglike_loc']
        else:
            o.loglike_ptsrc = fit_output['loglike']

        o.dloglike = o.loglike - o.loglike_ptsrc
        o.ts_ext = 2 * (o.loglike_ext - o.loglike_ptsrc)
        self.logger.debug('Point-Source Model Likelihood: %f', o.loglike_ptsrc)

        if kwargs['save_model_map']:
            o.ptsrc_tot_map = self.model_counts_map()
            o.ptsrc_src_map = self.model_counts_map(name)
            o.ptsrc_bkg_map = self.model_counts_map(exclude=[name])

        if update and (sqrt_ts_threshold is None
                       or np.sqrt(o['ts_ext']) > sqrt_ts_threshold):
            src = self.delete_source(name, loglevel=logging.DEBUG)
            src.set_spatial_model(spatial_model, {
                'ra': o.ra,
                'dec': o.dec,
                'SpatialWidth': o.ext
            })
            # FIXME: Issue with source map cache with source is
            # initialized as fixed.
            self.add_source(name, src, free=True, loglevel=logging.DEBUG)
            self.free_source(name, loglevel=logging.DEBUG)
            if fix_shape:
                self.free_source(name,
                                 free=False,
                                 pars='shape',
                                 loglevel=logging.DEBUG)
            fit_output = self.fit(loglevel=logging.DEBUG,
                                  **kwargs['optimizer'])
            o.loglike_ext = fit_output['loglike']

            src = self.roi.get_source_by_name(name)
            if kwargs['fit_position']:
                for k in [
                        'ra_err', 'dec_err', 'glon_err', 'glat_err', 'pos_err',
                        'pos_err_semimajor', 'pos_err_semiminor', 'pos_r68',
                        'pos_r95', 'pos_r99', 'pos_angle'
                ]:
                    src[k] = o[k]

        else:
            self.set_source_morphology(name,
                                       spatial_model=src['SpatialModel'],
                                       spatial_pars=src.spatial_pars,
                                       update_source=False)
            # Restore ROI to previous state
            saved_state.restore()
            self._sync_params(name)
            self._update_roi()

        self.logger.info('Best-fit extension: %6.4f + %6.4f - %6.4f' %
                         (o['ext'], o['ext_err_hi'], o['ext_err_lo']))
        self.logger.info('TS_ext:        %.3f' % o['ts_ext'])
        self.logger.info('Extension UL: %6.4f' % o['ext_ul95'])
        self.logger.info('LogLike: %12.3f DeltaLogLike: %12.3f', o.loglike_ext,
                         o.loglike_ext - o.loglike_init)

        if kwargs['fit_position']:
            self.logger.info(
                'Position:\n'
                '(  ra, dec) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                '(glon,glat) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                'offset = %8.4f r68 = %8.4f r95 = %8.4f r99 = %8.4f', o.ra,
                o.ra_err, o.dec, o.dec_err, o.glon, o.glon_err, o.glat,
                o.glat_err, o.pos_offset, o.pos_r68, o.pos_r95, o.pos_r99)

        return o
Beispiel #3
0
    def _localize(self, name, **kwargs):

        nstep = kwargs.get('nstep')
        dtheta_max = kwargs.get('dtheta_max')
        update = kwargs.get('update', True)
        prefix = kwargs.get('prefix', '')
        use_cache = kwargs.get('use_cache', False)
        free_background = kwargs.get('free_background', False)
        free_radius = kwargs.get('free_radius', None)

        saved_state = LikelihoodState(self.like)

        if not free_background:
            self.free_sources(free=False, loglevel=logging.DEBUG)

        if free_radius is not None:
            diff_sources = [s.name for s in self.roi.sources if s.diffuse]
            skydir = self.roi[name].skydir
            free_srcs = [s.name for s in
                         self.roi.get_sources(skydir=skydir,
                                              distance=free_radius,
                                              exclude=diff_sources)]
            self.free_sources_by_name(free_srcs, pars='norm',
                                      loglevel=logging.DEBUG)

        src = self.roi.copy_source(name)
        skydir = src.skydir
        skywcs = self._skywcs
        src_pix = skydir.to_pixel(skywcs)

        fit0 = self._fit_position_tsmap(name, prefix=prefix,
                                        dtheta_max=dtheta_max,
                                        zmin=-3.0,
                                        use_pylike=False)

        self.logger.debug('Completed localization with TS Map.\n'
                          '(ra,dec) = (%10.4f,%10.4f) '
                          '(glon,glat) = (%10.4f,%10.4f)',
                          fit0['ra'], fit0['dec'],
                          fit0['glon'], fit0['glat'])

        # Fit baseline (point-source) model
        self.free_norm(name)
        fit_output = self._fit(loglevel=logging.DEBUG, **
                               kwargs.get('optimizer', {}))

        # Save likelihood value for baseline fit
        loglike0 = fit_output['loglike']
        self.logger.debug('Baseline Model Likelihood: %f', loglike0)

        o = defaults.make_default_tuple(defaults.localize_output)
        o.name = name
        o.config = kwargs
        o.fit_success = True
        o.loglike_base = loglike0
        o.loglike_loc = np.nan
        o.dloglike_loc = np.nan

        if fit0['fit_success']:
            scan_cdelt = 2.0 * fit0['pos_r95'] / (nstep - 1.0)
        else:
            scan_cdelt = np.abs(skywcs.wcs.cdelt[0])

        self.logger.debug('Refining localization search to '
                          'region of width: %.4f deg',
                          scan_cdelt * nstep)

        fit1 = self._fit_position_scan(name,
                                       skydir=fit0['skydir'],
                                       scan_cdelt=scan_cdelt,
                                       **kwargs)

        o.loglike_loc = fit1['loglike']
        o.dloglike_loc = o.loglike_loc - o.loglike_base
        o.tsmap = fit0.pop('tsmap')
        o.tsmap_peak = fit1.pop('tsmap')
        # o.update(fit1)

        # Best fit position and uncertainty from fit to TS map
        o.fit_init = fit0

        # Best fit position and uncertainty from pylike scan
        o.fit_scan = fit1
        o.update(fit1)

        cdelt0 = np.abs(skywcs.wcs.cdelt[0])
        cdelt1 = np.abs(skywcs.wcs.cdelt[1])
        pix = fit1['skydir'].to_pixel(skywcs)
        o.pos_offset = skydir.separation(fit1['skydir']).deg
        o.xpix = float(pix[0])
        o.ypix = float(pix[1])
        o.deltax = (o.xpix - src_pix[0]) * cdelt0
        o.deltay = (o.ypix - src_pix[1]) * cdelt1

        o.ra_preloc = skydir.ra.deg
        o.dec_preloc = skydir.dec.deg
        o.glon_preloc = skydir.galactic.l.deg
        o.glat_preloc = skydir.galactic.b.deg

        if o.pos_offset > dtheta_max:
            o.fit_success = False

        self.logger.info('Localization completed with new position:\n'
                         '(  ra, dec) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                         '(glon,glat) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                         'offset = %8.4f r68 = %8.4f r95 = %8.4f r99 = %8.4f',
                         o.ra, o.ra_err, o.dec, o.dec_err,
                         o.glon, o.glon_err, o.glat, o.glat_err,
                         o.pos_offset, o.pos_r68, o.pos_r95, o.pos_r99)

        if not o.fit_success:
            self.logger.warning('Fit to localization contour failed.')
        elif not o.fit_inbounds:
            self.logger.warning('Best-fit position outside of search region.')
        else:
            self.logger.info('Localization succeeded.')

        if update and ((not o.fit_success) or (not o.fit_inbounds)):
            self.logger.warning(
                'Localization failed.  Keeping existing position.')

        if update and o.fit_success and o.fit_inbounds:
            self.logger.info('Updating source %s '
                             'to localized position.', name)
            src = self.delete_source(name)
            src.set_position(fit1['skydir'])
            self.add_source(name, src, free=True)
            fit_output = self.fit(loglevel=logging.DEBUG)
            o.loglike_loc = fit_output['loglike']
            o.dloglike_loc = o.loglike_loc - o.loglike_base
            src = self.roi.get_source_by_name(name)
            self.logger.info('LogLike: %12.3f DeltaLogLike: %12.3f',
                             o.loglike_loc, o.dloglike_loc)

            src['glon_err'] = o.glon_err
            src['glat_err'] = o.glat_err
            src['ra_err'] = o.glon_err
            src['dec_err'] = o.glat_err
            src['pos_err'] = o.pos_err
            src['pos_err_semimajor'] = o.pos_err_semimajor
            src['pos_err_semiminor'] = o.pos_err_semiminor
            src['pos_r68'] = o.pos_r68
            src['pos_r95'] = o.pos_r95
            src['pos_r99'] = o.pos_r99
            src['pos_angle'] = o.pos_angle
            src['pos_gal_cov'] = o.pos_gal_cov
            src['pos_gal_corr'] = o.pos_gal_corr
            src['pos_cel_cov'] = o.pos_cel_cov
            src['pos_cel_corr'] = o.pos_cel_corr
        else:
            saved_state.restore()
            self._sync_params(name)
            self._update_roi()

        return o
Beispiel #4
0
    def _extension(self, name, **kwargs):

        spatial_model = kwargs['spatial_model']
        width_min = kwargs['width_min']
        width_max = kwargs['width_max']
        width_nstep = kwargs['width_nstep']
        width = kwargs['width']
        free_background = kwargs['free_background']
        free_radius = kwargs.get('free_radius', None)
        fix_shape = kwargs.get('fix_shape', False)
        make_tsmap = kwargs.get('make_tsmap', False)
        update = kwargs['update']
        sqrt_ts_threshold = kwargs['sqrt_ts_threshold']

        if kwargs['psf_scale_fn']:
            def psf_scale_fn(t): return 1.0 + np.interp(np.log10(t),
                                                        kwargs['psf_scale_fn'][0],
                                                        kwargs['psf_scale_fn'][1])
        else:
            psf_scale_fn = None

        saved_state = LikelihoodState(self.like)
        loglike_init = -self.like()
        self.logger.debug('Initial Model Log-Likelihood: %f', loglike_init)

        if not free_background:
            self.free_sources(free=False, loglevel=logging.DEBUG)

        if free_radius is not None:
            diff_sources = [s.name for s in self.roi.sources if s.diffuse]
            skydir = self.roi[name].skydir
            free_srcs = [s.name for s in
                         self.roi.get_sources(skydir=skydir,
                                              distance=free_radius,
                                              exclude=diff_sources)]
            self.free_sources_by_name(free_srcs, pars='norm',
                                      loglevel=logging.DEBUG)

        # Fit baseline model
        self.free_source(name, loglevel=logging.DEBUG)
        if fix_shape:
            self.free_source(name, free=False, pars='shape',
                             loglevel=logging.DEBUG)

        fit_output = self._fit(loglevel=logging.DEBUG, **kwargs['optimizer'])
        src = self.roi.copy_source(name)

        # Save likelihood value for baseline fit
        saved_state_base = LikelihoodState(self.like)
        loglike_base = fit_output['loglike']
        self.logger.debug('Baseline Model Log-Likelihood: %f', loglike_base)

        if not width:
            width = np.logspace(np.log10(width_min), np.log10(width_max),
                                width_nstep)

        width = np.array(width)
        width = width[width > 0]
        width = np.concatenate(([0.0], np.array(width)))

        o = defaults.make_default_tuple(defaults.extension_output)
        o.name = name
        o.width = width
        o.dloglike = np.zeros(len(width) + 1)
        o.loglike = np.zeros(len(width) + 1)
        o.loglike_base = loglike_base
        o.loglike_init = loglike_init
        o.config = kwargs
        o.ebin_ext = np.ones(self.enumbins) * np.nan
        o.ebin_ext_err = np.ones(self.enumbins) * np.nan
        o.ebin_ext_err_lo = np.ones(self.enumbins) * np.nan
        o.ebin_ext_err_hi = np.ones(self.enumbins) * np.nan
        o.ebin_ext_ul95 = np.ones(self.enumbins) * np.nan
        o.ebin_ts_ext = np.ones(self.enumbins) * np.nan
        o.ebin_loglike = np.ones((self.enumbins, len(width))) * np.nan
        o.ebin_dloglike = np.ones((self.enumbins, len(width))) * np.nan
        o.ebin_loglike_ptsrc = np.ones(self.enumbins) * np.nan
        o.ebin_loglike_ext = np.ones(self.enumbins) * np.nan
        o.ebin_e_min = self.energies[:-1]
        o.ebin_e_max = self.energies[1:]
        o.ebin_e_ctr = np.sqrt(o.ebin_e_min * o.ebin_e_max)

        self.logger.debug('Width scan vector:\n %s', width)

        if kwargs['fit_position']:
            ext_fit = self._fit_extension_full(name,
                                               spatial_model=spatial_model,
                                               optimizer=kwargs['optimizer'])
        else:
            ext_fit = self._fit_extension(name,
                                          spatial_model=spatial_model,
                                          optimizer=kwargs['optimizer'],
                                          psf_scale_fn=psf_scale_fn)

        o.update(ext_fit)

        # Fit with the best-fit extension model
        self.logger.info('Fitting extended-source model.')

        self.set_source_morphology(name, spatial_model=spatial_model,
                                   spatial_pars={'ra': o['ra'], 'dec': o['dec'],
                                                 'SpatialWidth': o['ext']},
                                   use_pylike=False,
                                   psf_scale_fn=psf_scale_fn)

        # Perform scan over width parameter
        o.loglike = self._scan_extension(name,
                                         spatial_model=spatial_model,
                                         width=width,
                                         optimizer=kwargs['optimizer'],
                                         psf_scale_fn=psf_scale_fn)

        self.set_source_morphology(name, spatial_model=spatial_model,
                                   spatial_pars={'ra': o['ra'], 'dec': o['dec'],
                                                 'SpatialWidth': o['ext']},
                                   use_pylike=False,
                                   psf_scale_fn=psf_scale_fn)

        fit_output = self._fit(loglevel=logging.DEBUG, update=False,
                               **kwargs['optimizer'])

        o.source_fit = self.get_src_model(name, reoptimize=True,
                                          optimizer=kwargs['optimizer'])
        o.loglike_ext = fit_output['loglike']

        if kwargs['fit_ebin']:
            self._fit_extension_ebin(name, o, **kwargs)

        if kwargs['save_model_map']:
            o.ext_tot_map = self.model_counts_map()
            o.ext_src_map = self.model_counts_map(name)
            o.ext_bkg_map = self.model_counts_map(exclude=[name])

        if make_tsmap:
            tsmap_model = {'SpatialModel': 'RadialDisk',
                           'SpatialWidth': 0.1 * 0.8246211251235321}
            tsmap_model.update(src.spectral_pars)
            self.logger.info('Generating TS map.')
            tsmap = self.tsmap(model=tsmap_model,
                               map_skydir=SkyCoord(
                                   o['ra'], o['dec'], unit='deg'),
                               map_size=max(1.0, 4.0 * o['ext']),
                               exclude=[name],
                               write_fits=False,
                               write_npy=False,
                               use_pylike=False,
                               make_plots=False,
                               loglevel=logging.DEBUG)
            o.tsmap = tsmap['ts']

        self.logger.info('Testing point-source model.')
        # Test point-source hypothesis
        self.set_source_morphology(name, spatial_model='PointSource',
                                   use_pylike=False,
                                   psf_scale_fn=psf_scale_fn)

        # Fit a point-source
        saved_state_base.restore()
        self.logger.debug('Fitting point-source model.')
        fit_output = self._fit(loglevel=logging.DEBUG, **kwargs['optimizer'])

        if src['SpatialModel'] == 'PointSource' and kwargs['fit_position']:
            loc = self.localize(name, update=False)
            o.loglike_ptsrc = loc['loglike_loc']
        else:
            o.loglike_ptsrc = fit_output['loglike']

        o.dloglike = o.loglike - o.loglike_ptsrc
        o.ts_ext = 2 * (o.loglike_ext - o.loglike_ptsrc)
        self.logger.debug('Point-Source Model Likelihood: %f', o.loglike_ptsrc)

        if kwargs['save_model_map']:
            o.ptsrc_tot_map = self.model_counts_map()
            o.ptsrc_src_map = self.model_counts_map(name)
            o.ptsrc_bkg_map = self.model_counts_map(exclude=[name])

        if update and (sqrt_ts_threshold is None or
                       np.sqrt(o['ts_ext']) > sqrt_ts_threshold):
            src = self.delete_source(name, loglevel=logging.DEBUG)
            src.set_spatial_model(spatial_model,
                                  {'ra': o.ra, 'dec': o.dec,
                                   'SpatialWidth': o.ext})
            # FIXME: Issue with source map cache with source is
            # initialized as fixed.
            self.add_source(name, src, free=True, loglevel=logging.DEBUG)
            self.free_source(name, loglevel=logging.DEBUG)
            if fix_shape:
                self.free_source(name, free=False, pars='shape',
                                 loglevel=logging.DEBUG)
            fit_output = self.fit(loglevel=logging.DEBUG,
                                  **kwargs['optimizer'])
            o.loglike_ext = fit_output['loglike']

            src = self.roi.get_source_by_name(name)
            if kwargs['fit_position']:
                for k in ['ra_err', 'dec_err', 'glon_err', 'glat_err',
                          'pos_err', 'pos_err_semimajor', 'pos_err_semiminor',
                          'pos_r68', 'pos_r95', 'pos_r99', 'pos_angle']:
                    src[k] = o[k]

        else:
            self.set_source_morphology(name, spatial_model=src['SpatialModel'],
                                       spatial_pars=src.spatial_pars,
                                       update_source=False)
            # Restore ROI to previous state
            saved_state.restore()
            self._sync_params(name)
            self._update_roi()

        self.logger.info('Best-fit extension: %6.4f + %6.4f - %6.4f'
                         % (o['ext'], o['ext_err_hi'], o['ext_err_lo']))
        self.logger.info('TS_ext:        %.3f' % o['ts_ext'])
        self.logger.info('Extension UL: %6.4f' % o['ext_ul95'])
        self.logger.info('LogLike: %12.3f DeltaLogLike: %12.3f',
                         o.loglike_ext, o.loglike_ext - o.loglike_init)

        if kwargs['fit_position']:
            self.logger.info('Position:\n'
                             '(  ra, dec) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                             '(glon,glat) = (%10.4f +/- %8.4f,%10.4f +/- %8.4f)\n'
                             'offset = %8.4f r68 = %8.4f r95 = %8.4f r99 = %8.4f',
                             o.ra, o.ra_err, o.dec, o.dec_err,
                             o.glon, o.glon_err, o.glat, o.glat_err,
                             o.pos_offset, o.pos_r68, o.pos_r95, o.pos_r99)

        return o