Source code for stginga.plugins.local_plugin_mixin

"""Mixin classes for local plugins for Ginga."""

# STDLIB
import json
import os

# THIRD-PARTY
from astropy.utils.misc import JsonCustomEncoder

# GINGA
from ginga.gw import Widgets
from ginga.misc.Future import Future

# Need this for RTD to build successfully without Qt
try:
    from ginga.gw.GwHelp import FileSelection
except ImportError:
    pass

# STGINGA
from stginga import utils

__all__ = ['HelpMixin', 'MEFMixin', 'ParamMixin']


[docs] class HelpMixin(object):
[docs] def help(self): """Display online help for the plugin.""" if not hasattr(self.fv, 'help_plugin'): # ginga < v5.x, open in external browser import webbrowser webbrowser.open(self.help_url) return # ginga v5.x self.fv.help_plugin(self, url=self.help_url)
[docs] class MEFMixin(object): """Mixin class for Ginga local plugin that enables manipulation of multi-extension FITS images. """
[docs] def general_mef_settings(self, prefs): """Load MEF settings. Sets the following internal variables from Ginga's general user preferences:: self._sci_extname self._err_extname self._dq_extname self._ext_key self._extver_key self._ins_key self._tel_key Also sets the following:: self._no_keyword Parameters ---------- prefs : ``ginga.misc.Settings.Preferences`` Ginga preferences from ``self.fv.get_preferences()``. """ self._no_keyword = 'N/A' gen_settings = prefs.create_category('general') gen_settings.load(onError='silent') self._sci_extname = gen_settings.get('sciextname', 'SCI') self._err_extname = gen_settings.get('errextname', 'ERR') self._dq_extname = gen_settings.get('dqextname', 'DQ') self._ext_key = gen_settings.get('extnamekey', 'EXTNAME') self._extver_key = gen_settings.get('extverkey', 'EXTVER') self._tel_key = gen_settings.get('telescopekey', 'TELESCOP') self._ins_key = gen_settings.get('instrumentkey', 'INSTRUME')
def _info_for_other_ext(self, image, header): """Extract relevant metadata for loading another extension.""" imfile = image.metadata['path'] imname = image.metadata['name'].split('[')[0] telescope = header.get(self._tel_key, None) instrument = header.get(self._ins_key, None) extver = header.get(self._extver_key, 0) return imfile, imname, telescope, instrument, extver
[docs] def load_err(self, image, header): """Find and load ERR extension. .. note:: WFPC2 does not have ERR. Parameters ---------- image : ``ginga.AstroImage.AstroImage`` Ginga image object. header : dict Header associated with the image. Returns ------- errsrc : ``ginga.AstroImage.AstroImage`` or `False` ERR image associated with given image, if available. """ imfile, imname, telescope, instrument, extver = self._info_for_other_ext(image, header) # noqa if telescope == 'HST' and instrument == 'WFPC2': return False err_extnum = (self._err_extname, extver) errname = f'{imname}[{self._err_extname},{extver}]' errsrc = utils.find_ext(imfile, err_extnum) # Load ERR image if errsrc: errsrc = self.autoload_ginga_image(imfile, err_extnum, errname) else: self.logger.warn(f'{err_extnum} extension not found for {imfile}') return errsrc
[docs] def load_dq(self, image, header): """Find and load DQ extension. **Special Handling for WFPC2** The DQ file has ``c1m`` in its name. However, extension name could be either ``'DQ'`` or ``'SCI'``. Parameters ---------- image : ``ginga.AstroImage.AstroImage`` Ginga image object. header : dict Header associated with the image. Returns ------- dqsrc : ``ginga.AstroImage.AstroImage`` or `False` DQ image associated with given image, if available. """ imfile, imname, telescope, instrument, extver = self._info_for_other_ext(image, header) # noqa dq_extnum = (self._dq_extname, extver) if telescope != 'HST' or instrument != 'WFPC2': dqname = f'{imname}[{self._dq_extname},{extver}]' dqsrc = utils.find_ext(imfile, dq_extnum) # Special handling for WFPC2, lots of assumptions else: imfile = imfile.replace('c0m', 'c1m') imname = imname.replace('c0m', 'c1m') dqsrc = utils.find_ext(imfile, dq_extnum) if not dqsrc: dq_extnum = (self._sci_extname, extver) dqsrc = utils.find_ext(imfile, dq_extnum) dqname = f'{imname}[{dq_extnum[0]},{extver}]' # Load DQ image if dqsrc: dqsrc = self.autoload_ginga_image(imfile, dq_extnum, dqname) else: self.logger.error(f'{dq_extnum} extension not found for {imfile}') return dqsrc
[docs] def autoload_ginga_image(self, filename, extnum, cachekey): """Automatically load a given image extension into Ginga viewer. Parameters ---------- filename : str Image filename. extnum : int Image extension number. cachekey : str Key for Ginga data cache. Usually, this is in the format of ``prefix[extname, extver]``. Returns ------- image : ``ginga.AstroImage.AstroImage`` Ginga image object. """ # Image already loaded if cachekey in self.chinfo.datasrc: self.logger.debug(f'Loading {cachekey} from cache') image = self.chinfo.datasrc[cachekey] # Auto load image data else: self.logger.debug( f'Loading {cachekey} from {filename}') image = self.fv.load_image(filename, idx=extnum) future = Future() future.freeze(self.fv.load_image, filename, idx=extnum) image.set(path=filename, idx=extnum, name=cachekey, image_future=future) self.fv.add_image(cachekey, image, chname=self.chname, silent=True) self.fv.advertise_image(self.chname, image) return image
[docs] class ParamMixin(object): """Mixin class for Ginga local plugin that enables the feature to save/load parameters. """
[docs] def build_param_gui(self, container): """Call this in ``build_gui()`` to create 'Load Param' and 'Save Param' buttons. Parameters ---------- container : widget The widget to contain these buttons. """ captions = (('Load Param', 'button', 'Save Param', 'button'), ) w, b = Widgets.build_info(captions, orientation=self.orientation) self.w.update(b) b.load_param.set_tooltip('Load previously saved parameters') b.load_param.add_callback( 'activated', lambda w: self.load_params_cb()) b.save_param.set_tooltip(f'Save {str(self)} parameters') b.save_param.add_callback( 'activated', lambda w: self.save_params()) container.add_widget(w, stretch=0) # Initialize file save dialog self.filesel = FileSelection(self.fv.w.root.get_widget())
[docs] def params_dict(self): """Return current parameters as a dictionary.""" raise NotImplementedError('To be implemented by Ginga local plugin')
[docs] def save_params(self): """Save parameters to a JSON file.""" pardict = self.params_dict() fname = Widgets.SaveDialog( title='Save parameters', selectedfilter='*.json').get_path() if fname is None: # Cancel return if os.path.exists(fname): self.logger.warn(f'{fname} will be overwritten') with open(fname, 'w') as fout: json.dump(pardict, fout, indent=4, sort_keys=True, cls=JsonCustomEncoder) self.logger.info(f'Parameters saved as {fname}')
[docs] def load_params_cb(self): """Allow user to select JSON file to load.""" self.filesel.popup('Load JSON file', self.load_params, initialdir='.', filename='JSON files (*.json)')
[docs] def load_params(self, filename): """Load previously saved parameters from a JSON file.""" if not os.path.isfile(filename): return True with open(filename) as fin: self.logger.info(f'{str(self)} parameters loaded from {filename}') pardict = json.load(fin) self.ingest_params(pardict)
[docs] def ingest_params(self, pardict): """Ingest dictionary containing plugin parameters into plugin GUI and internal variables.""" raise NotImplementedError('To be implemented by Ginga local plugin')