From 9cb0b76bcf1f746fd9ffa7fb28e214b59ce02ec2 Mon Sep 17 00:00:00 2001 From: ndilalla Date: Wed, 21 Jan 2026 16:48:07 -0800 Subject: [PATCH 1/3] Adding label_source keyword to select source to label in plots --- fermipy/defaults.py | 2 ++ fermipy/plotting.py | 44 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/fermipy/defaults.py b/fermipy/defaults.py index c7e08c80..32e803bb 100644 --- a/fermipy/defaults.py +++ b/fermipy/defaults.py @@ -783,6 +783,8 @@ def make_attrs_class(typename, d): 'figsize': ([8.0, 6.0], 'Set the default figure size.', list), 'label_ts_threshold': (0., 'TS threshold for labeling sources in sky maps. If None then no sources will be labeled.', float), + '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), } diff --git a/fermipy/plotting.py b/fermipy/plotting.py index f034c38e..a26713e4 100644 --- a/fermipy/plotting.py +++ b/fermipy/plotting.py @@ -378,6 +378,7 @@ class ROIPlotter(fermipy.config.Configurable): 'catalogs': (None, '', list), 'graticule_radii': (None, '', list), 'label_ts_threshold': (0.0, '', float), + 'label_source': (None, '', (str, list)), 'cmap': ('ds9_b', '', str), } @@ -534,6 +535,7 @@ def plot_roi(self, roi, **kwargs): src_color = 'w' label_ts_threshold = kwargs.get('label_ts_threshold', 0.0) + label_source = kwargs.get('label_source', None) plot_kwargs = dict(linestyle='None', marker='+', markerfacecolor='None', mew=0.66, ms=8, # markersize=8, @@ -543,16 +545,24 @@ def plot_roi(self, roi, **kwargs): fontweight='normal') ts = np.array([s['ts'] for s in roi.point_sources]) + skydir = roi._src_skydir + labels = [s.name for s in roi.point_sources] - if label_ts_threshold is None: + # If label_source is specified, only label those sources + if label_source is not None: + # Convert to list if it's a single string + if utils.isstr(label_source): + label_source = [label_source] + # Create mask for sources matching the specified names + m = np.array([s.name in label_source for s in roi.point_sources]) + # Otherwise use the TS threshold logic + elif label_ts_threshold is None: m = np.zeros(len(ts), dtype=bool) elif label_ts_threshold <= 0: m = np.ones(len(ts), dtype=bool) else: m = ts > label_ts_threshold - skydir = roi._src_skydir - labels = [s.name for s in roi.point_sources] self.plot_sources(skydir, labels, plot_kwargs, text_kwargs, label_mask=m, **kwargs) @@ -587,6 +597,7 @@ def plot(self, **kwargs): self.config['graticule_radii']) label_ts_threshold = kwargs.get('label_ts_threshold', self.config['label_ts_threshold']) + label_source = kwargs.get('label_source', None) im_kwargs = dict(cmap=self.config['cmap'], interpolation='nearest', transform=None, @@ -608,7 +619,8 @@ def plot(self, **kwargs): if self._roi is not None: self.plot_roi(self._roi, - label_ts_threshold=label_ts_threshold) + label_ts_threshold=label_ts_threshold, + label_source=label_source) self._extent = im.get_extent() ax.set_xlim(self._extent[0], self._extent[1]) @@ -979,6 +991,17 @@ def make_residmap_plots(self, maps, roi=None, **kwargs): Crop the image by this factor. If None then no crop is applied. + label_source : str or list, optional + Name(s) of source(s) to label on the plot. If specified, + only these sources will be labeled, overriding the + `label_ts_threshold` setting. Can be a single source name + (string) or a list of source names. + + label_ts_threshold : float, optional + TS threshold for labeling sources. Only used if + `label_source` is not specified. If None, no sources will + be labeled. If <= 0, all sources will be labeled. + """ fmt = kwargs.get('format', self.config['format']) @@ -992,6 +1015,7 @@ def make_residmap_plots(self, maps, roi=None, **kwargs): kwargs.setdefault('graticule_radii', self.config['graticule_radii']) kwargs.setdefault('label_ts_threshold', self.config['label_ts_threshold']) + kwargs.setdefault('label_source', self.config['label_source']) cmap = kwargs.setdefault('cmap', self.config['cmap']) cmap_resid = kwargs.pop('cmap_resid', self.config['cmap_resid']) kwargs.setdefault('catalogs', self.config['catalogs']) @@ -1113,10 +1137,22 @@ def make_tsmap_plots(self, maps, roi=None, **kwargs): zoom : float Crop the image by this factor. If None then no crop is applied. + + label_source : str or list, optional + Name(s) of source(s) to label on the plot. If specified, + only these sources will be labeled, overriding the + `label_ts_threshold` setting. Can be a single source name + (string) or a list of source names. + + label_ts_threshold : float, optional + TS threshold for labeling sources. Only used if + `label_source` is not specified. If None, no sources will + be labeled. If <= 0, all sources will be labeled. """ kwargs.setdefault('graticule_radii', self.config['graticule_radii']) kwargs.setdefault('label_ts_threshold', self.config['label_ts_threshold']) + kwargs.setdefault('label_source', self.config['label_source']) kwargs.setdefault('cmap', self.config['cmap']) kwargs.setdefault('catalogs', self.config['catalogs']) fmt = kwargs.get('format', self.config['format']) From ff8b3cc3deb4d32d4292eb11e9f27ec1cc2309f3 Mon Sep 17 00:00:00 2001 From: ndilalla Date: Wed, 18 Feb 2026 16:01:02 -0800 Subject: [PATCH 2/3] Adding the option to docs. --- docs/config/plotting.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/config/plotting.csv b/docs/config/plotting.csv index fb64c6de..dd60aefd 100644 --- a/docs/config/plotting.csv +++ b/docs/config/plotting.csv @@ -6,4 +6,5 @@ ``graticule_radii`` None Define a list of radii at which circular graticules will be drawn. ``interactive`` False Enable interactive mode. If True then plots will be drawn after each plotting command. ``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 From cb634918a0b05fe3a45790fa45032033bd4ee02f Mon Sep 17 00:00:00 2001 From: ndilalla Date: Mon, 23 Feb 2026 13:53:57 -0800 Subject: [PATCH 3/3] Fixed options and added a test. --- fermipy/config.py | 7 +++++ fermipy/plotting.py | 4 ++- fermipy/tests/test_gtanalysis.py | 13 +++++++++- fermipy/tests/test_plotting.py | 44 ++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 fermipy/tests/test_plotting.py diff --git a/fermipy/config.py b/fermipy/config.py index 979df839..40d38f5e 100644 --- a/fermipy/config.py +++ b/fermipy/config.py @@ -65,6 +65,13 @@ def validate_option(opt_name, opt_val, schema_type): if opt_val is None: return + # Union type: schema_type is a tuple of allowed types (e.g. (str, list)) + if isinstance(schema_type, tuple): + if type(opt_val) not in schema_type: + raise TypeError('Wrong type for %s %s (allowed: %s)' % + (opt_name, type(opt_val), schema_type)) + return + type_match = type(opt_val) is schema_type type_checks = (schema_type in [list, dict, bool] or type(opt_val) in [list, dict, bool]) diff --git a/fermipy/plotting.py b/fermipy/plotting.py index a26713e4..08673fdb 100644 --- a/fermipy/plotting.py +++ b/fermipy/plotting.py @@ -597,7 +597,7 @@ def plot(self, **kwargs): self.config['graticule_radii']) label_ts_threshold = kwargs.get('label_ts_threshold', self.config['label_ts_threshold']) - label_source = kwargs.get('label_source', None) + label_source = kwargs.get('label_source', self.config['label_source']) im_kwargs = dict(cmap=self.config['cmap'], interpolation='nearest', transform=None, @@ -1235,6 +1235,7 @@ def make_psmap_plots(self, psmaps, roi=None, **kwargs): kwargs.setdefault('graticule_radii', self.config['graticule_radii']) kwargs.setdefault('label_ts_threshold', self.config['label_ts_threshold']) + kwargs.setdefault('label_source', self.config['label_source']) kwargs.setdefault('cmap', self.config['cmap']) kwargs.setdefault('catalogs', self.config['catalogs']) fmt = kwargs.get('format', self.config['format']) @@ -1407,6 +1408,7 @@ def make_roi_plots(self, gta, mcube_tot, **kwargs): 'graticule_radii', self.config['graticule_radii']) roi_kwargs.setdefault('label_ts_threshold', self.config['label_ts_threshold']) + roi_kwargs.setdefault('label_source', self.config['label_source']) roi_kwargs.setdefault('cmap', self.config['cmap']) roi_kwargs.setdefault('catalogs', self._catalogs) diff --git a/fermipy/tests/test_gtanalysis.py b/fermipy/tests/test_gtanalysis.py index c4490c3f..dff16af4 100644 --- a/fermipy/tests/test_gtanalysis.py +++ b/fermipy/tests/test_gtanalysis.py @@ -162,7 +162,18 @@ def test_gtanalysis_fit_newton(create_diffuse_dir, create_draco_analysis): def test_gtanalysis_tsmap(create_diffuse_dir, create_draco_analysis): gta = create_draco_analysis gta.load_roi('fit1') - gta.tsmap(model={}, make_plots=True) + gta.tsmap(model={}, make_plots=True, prefix='tsmap_default') + gta.config['plotting']['label_source'] = None + gta.config['plotting']['label_ts_threshold'] = 25.0 + gta.tsmap(model={}, make_plots=True, prefix='tsmap_ts25') + + gta.config['plotting']['label_source'] = ['draco', + '4FGL J1741.2+5739', + '4FGL J1742.5+5944'] + gta.config['plotting']['label_ts_threshold'] = 0.0 + gta.tsmap(model={}, make_plots=True, prefix='tsmap_label_source') + gta.config['plotting']['label_source'] = None + def test_gtanalysis_psmap(create_diffuse_dir, create_draco_analysis): gta = create_draco_analysis diff --git a/fermipy/tests/test_plotting.py b/fermipy/tests/test_plotting.py new file mode 100644 index 00000000..a638f527 --- /dev/null +++ b/fermipy/tests/test_plotting.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, division, print_function + +import numpy as np + +from fermipy.plotting import ROIPlotter + + +class _DummySrc(dict): + def __init__(self, name, ts): + super(_DummySrc, self).__init__(ts=ts) + self.name = name + + +class _DummyROI(object): + def __init__(self): + self.point_sources = [ + _DummySrc('srcA', 10.0), + _DummySrc('srcB', 20.0), + _DummySrc('srcC', 5.0), + ] + self._src_skydir = None + + +class _MaskRecorder(object): + def __init__(self): + self.label_mask = None + + def plot_sources(self, skydir, labels, plot_kwargs, text_kwargs, **kwargs): + self.label_mask = kwargs.get('label_mask') + + +def test_plot_roi_label_source_and_ts_threshold(): + """Verify source labeling by explicit names and TS threshold.""" + roi = _DummyROI() + + # label_source path: only listed sources are labeled. + recorder = _MaskRecorder() + ROIPlotter.plot_roi(recorder, roi, label_source=['srcA', 'srcC']) + assert np.array_equal(recorder.label_mask, np.array([True, False, True])) + + # label_ts_threshold path: only sources above threshold are labeled. + recorder = _MaskRecorder() + ROIPlotter.plot_roi(recorder, roi, label_ts_threshold=12.0) + assert np.array_equal(recorder.label_mask, np.array([False, True, False]))