diff --git a/docs/config.rst b/docs/config.rst index fca9a244..a7322499 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -329,6 +329,9 @@ optimizer plotting -------- +The options in *plotting* control the default behavior of sky maps and +other plots. + .. csv-table:: *plotting* Options :header: Option, Default, Description :file: config/plotting.csv diff --git a/docs/config/plotting.csv b/docs/config/plotting.csv index dd60aefd..1370b134 100644 --- a/docs/config/plotting.csv +++ b/docs/config/plotting.csv @@ -8,3 +8,4 @@ ``label_ts_threshold`` 0.0 TS threshold for labeling sources in sky maps. If None then no sources will be labeled. ``label_source`` None Name(s) of source(s) to label on plots. If specified, only these sources will be labeled, overriding label_ts_threshold. Can be a single source name (string) or a list of source names. ``loge_bounds`` None +``ra_format`` hour Display RA coordinates on sky maps as hour angle (hh:mm:ss) or decimal degrees. Options: ``'hour'`` (default) or ``'deg'``. Applies to all 2D sky maps (psmap, tsmap, residmap, tscube, localization, extension, ROI plots). Can be overridden per method (e.g. in *psmap* config or by passing ``ra_format`` to map methods). diff --git a/fermipy/defaults.py b/fermipy/defaults.py index 32e803bb..88169e9f 100644 --- a/fermipy/defaults.py +++ b/fermipy/defaults.py @@ -65,6 +65,7 @@ def make_attrs_class(typename, d): 'By default the full analysis energy range will be used. If ' 'either emin/emax are None then only an upper/lower bound on ' 'the energy range wil be applied.', list), + 'ra_format': ('hour', 'Format for RA axis labels in plots. Options are "hour" (hh:mm:ss) or "deg" (degrees).', str), } # Options for defining input data files @@ -294,6 +295,7 @@ def make_attrs_class(typename, d): 'use_weights': common['use_weights'], 'write_fits': common['write_fits'], 'write_npy': common['write_npy'], + 'ra_format': common['ra_format'], } # TS Map @@ -310,6 +312,7 @@ def make_attrs_class(typename, d): 'make_plots': common['make_plots'], 'write_fits': common['write_fits'], 'write_npy': common['write_npy'], + 'ra_format': common['ra_format'], } # TS Cube @@ -330,6 +333,7 @@ def make_attrs_class(typename, d): 'remake_test_source': (False, 'If true, recomputes the test source image (otherwise just shifts it)', bool), 'st_scan_level': (0, 'Level to which to do ST-based fitting (for testing)', int), 'init_lambda': (0, 'Initial value of damping parameter for newton step size calculation. A value of zero disables damping.', float), + 'ra_format': common['ra_format'], } # PS map @@ -357,6 +361,7 @@ def make_attrs_class(typename, d): 'scaleaxis':(20,'LL computation: scale axis.', float), 'make_plots': common['make_plots'], 'write_fits': common['write_fits'], + 'ra_format': common['ra_format'], } # Options for Source Finder @@ -588,6 +593,7 @@ def make_attrs_class(typename, d): 'write_fits': common['write_fits'], 'write_npy': common['write_npy'], 'reoptimize':(False, 'Re-fit ROI in each energy bin. No effect if fit_ebin=False or there are no free parameters', bool ), + 'ra_format': common['ra_format'], } # Options for localization analysis @@ -606,6 +612,7 @@ def make_attrs_class(typename, d): 'make_plots': common['make_plots'], 'write_fits': common['write_fits'], 'write_npy': common['write_npy'], + 'ra_format': common['ra_format'], } # Output for localization analysis @@ -786,6 +793,7 @@ def make_attrs_class(typename, d): 'label_source': (None, 'Name(s) of source(s) to label on plots. If specified, only these sources will be labeled, overriding label_ts_threshold. Can be a single source name (string) or a list of source names.', (str, list)), 'interactive': (False, 'Enable interactive mode. If True then plots will be drawn after each plotting command.', bool), + 'ra_format': common['ra_format'], } # Source dictionary diff --git a/fermipy/extension.py b/fermipy/extension.py index c6686368..172d084a 100644 --- a/fermipy/extension.py +++ b/fermipy/extension.py @@ -84,8 +84,11 @@ def extension(self, name, **kwargs): self.logger.info('Finished extension fit.') if config['make_plots']: - self._plotter.make_extension_plots(ext, self.roi, - prefix=config['prefix']) + # Pass ra_format from extension config if specified + plot_kwargs = {'prefix': config['prefix']} + if 'ra_format' in config: + plot_kwargs['ra_format'] = config['ra_format'] + self._plotter.make_extension_plots(ext, self.roi, **plot_kwargs) outfile = config.get('outfile', None) if outfile is None: diff --git a/fermipy/plotting.py b/fermipy/plotting.py index 08673fdb..e499bc13 100644 --- a/fermipy/plotting.py +++ b/fermipy/plotting.py @@ -289,6 +289,7 @@ def plot(self, subplot=111, cmap='magma', **kwargs): zscale = kwargs.get('zscale', 'lin') gamma = kwargs.get('gamma', 0.5) transform = kwargs.get('transform', None) + ra_format = kwargs.get('ra_format', 'hour') vmin = kwargs.get('vmin', None) vmax = kwargs.get('vmax', None) @@ -337,6 +338,9 @@ def plot(self, subplot=111, cmap='magma', **kwargs): if frame == 'icrs': ax.set_xlabel('RA') ax.set_ylabel('DEC') + # Set RA format based on configuration + if ra_format == 'deg': + ax.coords[0].set_major_formatter('d.ddd') elif frame == 'galactic': ax.set_xlabel('GLON') ax.set_ylabel('GLAT') @@ -380,6 +384,7 @@ class ROIPlotter(fermipy.config.Configurable): 'label_ts_threshold': (0.0, '', float), 'label_source': (None, '', (str, list)), 'cmap': ('ds9_b', '', str), + 'ra_format': ('hour', '', str), } def __init__(self, data_map, proj='AIT', **kwargs): @@ -598,11 +603,13 @@ def plot(self, **kwargs): label_ts_threshold = kwargs.get('label_ts_threshold', self.config['label_ts_threshold']) label_source = kwargs.get('label_source', self.config['label_source']) + ra_format = kwargs.get('ra_format', self.config['ra_format']) im_kwargs = dict(cmap=self.config['cmap'], interpolation='nearest', transform=None, vmin=None, vmax=None, clip=False, levels=None, - zscale='lin', subplot=111, colors=['k']) + zscale='lin', subplot=111, colors=['k'], + ra_format=ra_format) cb_kwargs = dict(orientation='vertical', shrink=1.0, pad=0.1, fraction=0.1, cb_label=None) @@ -1019,6 +1026,7 @@ def make_residmap_plots(self, maps, roi=None, **kwargs): cmap = kwargs.setdefault('cmap', self.config['cmap']) cmap_resid = kwargs.pop('cmap_resid', self.config['cmap_resid']) kwargs.setdefault('catalogs', self.config['catalogs']) + kwargs.setdefault('ra_format', self.config['ra_format']) if no_contour: sigma_levels = None else: @@ -1155,6 +1163,7 @@ def make_tsmap_plots(self, maps, roi=None, **kwargs): kwargs.setdefault('label_source', self.config['label_source']) kwargs.setdefault('cmap', self.config['cmap']) kwargs.setdefault('catalogs', self.config['catalogs']) + kwargs.setdefault('ra_format', self.config['ra_format']) fmt = kwargs.get('format', self.config['format']) figsize = kwargs.get('figsize', self.config['figsize']) workdir = kwargs.pop('workdir', self.config['fileio']['workdir']) @@ -1238,6 +1247,7 @@ def make_psmap_plots(self, psmaps, roi=None, **kwargs): kwargs.setdefault('label_source', self.config['label_source']) kwargs.setdefault('cmap', self.config['cmap']) kwargs.setdefault('catalogs', self.config['catalogs']) + kwargs.setdefault('ra_format', self.config['ra_format']) fmt = kwargs.get('format', self.config['format']) figsize = kwargs.get('figsize', self.config['figsize']) workdir = kwargs.pop('workdir', self.config['fileio']['workdir']) @@ -1411,6 +1421,7 @@ def make_roi_plots(self, gta, mcube_tot, **kwargs): roi_kwargs.setdefault('label_source', self.config['label_source']) roi_kwargs.setdefault('cmap', self.config['cmap']) roi_kwargs.setdefault('catalogs', self._catalogs) + roi_kwargs.setdefault('ra_format', kwargs.get('ra_format', self.config['ra_format'])) if loge_bounds is None: loge_bounds = (gta.log_energies[0], gta.log_energies[-1]) @@ -1511,6 +1522,7 @@ def make_localization_plots(self, loc, roi=None, **kwargs): prefix = kwargs.get('prefix', '') skydir = kwargs.get('skydir', None) cmap = kwargs.get('cmap', self.config['cmap']) + ra_format = kwargs.get('ra_format', self.config['ra_format']) name = loc.get('name', '') name = name.lower().replace(' ', '_') @@ -1526,7 +1538,7 @@ def make_localization_plots(self, loc, roi=None, **kwargs): path_effect = PathEffects.withStroke(linewidth=2.0, foreground="black") - p = ROIPlotter(tsmap_renorm, roi=roi) + p = ROIPlotter(tsmap_renorm, roi=roi, ra_format=ra_format) fig = plt.figure(figsize=figsize) vmin = max(-100.0, np.min(tsmap_renorm.data)) @@ -1601,7 +1613,7 @@ def make_localization_plots(self, loc, roi=None, **kwargs): tsmap_renorm = copy.deepcopy(tsmap) tsmap_renorm.data -= np.max(tsmap_renorm.data) - p = ROIPlotter(tsmap_renorm, roi=roi) + p = ROIPlotter(tsmap_renorm, roi=roi, ra_format=ra_format) fig = plt.figure(figsize=figsize) vmin = max(-50.0, np.min(tsmap_renorm.data)) @@ -1779,10 +1791,11 @@ def _plot_extension_tsmap(self, ext, roi=None, **kwargs): figsize = kwargs.get('figsize', self.config['figsize']) prefix = kwargs.get('prefix', '') cmap = kwargs.get('cmap', self.config['cmap']) + ra_format = kwargs.get('ra_format', self.config['ra_format']) name = ext.get('name', '') name = name.lower().replace(' ', '_') - p = ROIPlotter(ext['tsmap'], roi=roi) + p = ROIPlotter(ext['tsmap'], roi=roi, ra_format=ra_format) fig = plt.figure(figsize=figsize) sigma_levels = [3, 5, 7] + list(np.logspace(1, 3, 17)) diff --git a/fermipy/psmap.py b/fermipy/psmap.py index 675cf7a9..ba7ce16e 100644 --- a/fermipy/psmap.py +++ b/fermipy/psmap.py @@ -87,7 +87,11 @@ def psmap(self, prefix='', **kwargs): plotter = plotting.AnalysisPlotter(self.config['plotting'], fileio=self.config['fileio'], logging=self.config['logging']) - plotter.make_psmap_plots(o, self.roi) + # Pass ra_format from psmap config if specified + plot_kwargs = {} + if 'ra_format' in config: + plot_kwargs['ra_format'] = config['ra_format'] + plotter.make_psmap_plots(o, self.roi, **plot_kwargs) self.logger.log(config['loglevel'], 'Finished PS map') return o diff --git a/fermipy/residmap.py b/fermipy/residmap.py index b36d40a7..fc125201 100644 --- a/fermipy/residmap.py +++ b/fermipy/residmap.py @@ -270,7 +270,11 @@ def residmap(self, prefix='', **kwargs): fileio=self.config['fileio'], logging=self.config['logging']) - plotter.make_residmap_plots(o, self.roi) + # Pass ra_format from residmap config if specified + plot_kwargs = {} + if 'ra_format' in config: + plot_kwargs['ra_format'] = config['ra_format'] + plotter.make_residmap_plots(o, self.roi, **plot_kwargs) self.logger.info('Finished residual maps') diff --git a/fermipy/sourcefind.py b/fermipy/sourcefind.py index a3a03877..f1ec8925 100644 --- a/fermipy/sourcefind.py +++ b/fermipy/sourcefind.py @@ -287,8 +287,11 @@ def localize(self, name, **kwargs): self.logger.info('Finished localization.') if config['make_plots']: - self._plotter.make_localization_plots(loc, self.roi, - prefix=config['prefix']) + # Pass ra_format from localize config if specified + plot_kwargs = {'prefix': config['prefix']} + if 'ra_format' in config: + plot_kwargs['ra_format'] = config['ra_format'] + self._plotter.make_localization_plots(loc, self.roi, **plot_kwargs) outfile = \ utils.format_filename(self.workdir, 'loc', diff --git a/fermipy/tests/test_gtanalysis.py b/fermipy/tests/test_gtanalysis.py index 35da766a..11e5f0b6 100644 --- a/fermipy/tests/test_gtanalysis.py +++ b/fermipy/tests/test_gtanalysis.py @@ -175,7 +175,6 @@ def test_gtanalysis_tsmap(create_diffuse_dir, create_draco_analysis): gta.config['plotting']['label_source'] = None - def test_gtanalysis_psmap(create_diffuse_dir, create_draco_analysis): gta = create_draco_analysis gta.load_roi('fit1') diff --git a/fermipy/tests/test_plotting.py b/fermipy/tests/test_plotting.py index a638f527..1eb7b7f9 100644 --- a/fermipy/tests/test_plotting.py +++ b/fermipy/tests/test_plotting.py @@ -2,6 +2,8 @@ import numpy as np +from fermipy import config +from fermipy import defaults from fermipy.plotting import ROIPlotter @@ -42,3 +44,74 @@ def test_plot_roi_label_source_and_ts_threshold(): recorder = _MaskRecorder() ROIPlotter.plot_roi(recorder, roi, label_ts_threshold=12.0) assert np.array_equal(recorder.label_mask, np.array([False, True, False])) + + +def test_plotting_ra_format_default(): + """Test that ra_format has correct default value in plotting config.""" + plotting_defaults = defaults.plotting + assert 'ra_format' in plotting_defaults + assert plotting_defaults['ra_format'][0] == 'hour' + + +def test_psmap_ra_format_default(): + """Test that ra_format has correct default value in psmap config.""" + psmap_defaults = defaults.psmap + assert 'ra_format' in psmap_defaults + assert psmap_defaults['ra_format'][0] == 'hour' + + +def test_ra_format_config_values(): + """Test that ra_format defaults and schema metadata are valid.""" + plotting_defaults = defaults.plotting + + # Check default. + default_value = plotting_defaults['ra_format'][0] + assert default_value in ['hour', 'deg'] + + # Check description exists. + description = plotting_defaults['ra_format'][1] + assert len(description) > 0 + assert 'hour' in description.lower() or 'deg' in description.lower() + + # Check type. + value_type = plotting_defaults['ra_format'][2] + assert value_type == str + + +def test_ra_format_in_roiplotter_defaults(): + """Test that ROIPlotter has ra_format in its defaults.""" + assert 'ra_format' in ROIPlotter.defaults + default_value = ROIPlotter.defaults['ra_format'][0] + assert default_value == 'hour' + + +def test_plotting_config_creation(): + """Test that plotting configuration can be created with ra_format.""" + schema = config.ConfigSchema(defaults.plotting) + cfg = schema.create_config({'ra_format': 'deg'}) + assert cfg['ra_format'] == 'deg' + + # Test with default value. + cfg_default = schema.create_config({}) + assert cfg_default['ra_format'] == 'hour' + + +def test_psmap_config_creation(): + """Test that psmap configuration can be created with ra_format.""" + schema = config.ConfigSchema(defaults.psmap) + cfg = schema.create_config({'ra_format': 'deg'}) + assert cfg['ra_format'] == 'deg' + + # Test with default value. + cfg_default = schema.create_config({}) + assert cfg_default['ra_format'] == 'hour' + + +def test_ra_format_override(): + """Test that ra_format can be overridden in configuration.""" + schema = config.ConfigSchema(defaults.plotting) + base_cfg = schema.create_config({'ra_format': 'hour'}) + assert base_cfg['ra_format'] == 'hour' + + override_cfg = schema.create_config(base_cfg, ra_format='deg') + assert override_cfg['ra_format'] == 'deg' diff --git a/fermipy/tsmap.py b/fermipy/tsmap.py index 1641eabd..e0c41aea 100644 --- a/fermipy/tsmap.py +++ b/fermipy/tsmap.py @@ -700,7 +700,11 @@ def tsmap(self, prefix='', **kwargs): fileio=self.config['fileio'], logging=self.config['logging']) - plotter.make_tsmap_plots(o, self.roi) + # Pass ra_format from tsmap config if specified + plot_kwargs = {} + if 'ra_format' in config: + plot_kwargs['ra_format'] = config['ra_format'] + plotter.make_tsmap_plots(o, self.roi, **plot_kwargs) self.logger.log(config['loglevel'], 'Finished TS map') @@ -1030,7 +1034,11 @@ def tscube(self, prefix='', **kwargs): fileio=self.config['fileio'], logging=self.config['logging']) - plotter.make_tsmap_plots(maps, self.roi, suffix='tscube') + # Pass ra_format from tscube config if specified + plot_kwargs = {'suffix': 'tscube'} + if 'ra_format' in config: + plot_kwargs['ra_format'] = config['ra_format'] + plotter.make_tsmap_plots(maps, self.roi, **plot_kwargs) self.logger.info("Finished TS cube") return maps