Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LoaderConfig plugin to the Reference Viewer #1076

Merged
merged 3 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/WhatsNew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Ver 5.0.0 (unreleased)
same as before) to better reflect what the mode does
- Changed icons and cursors from PNG (bitmap) to SVG (vector) format
- Added color distribution ("stretch") control to Info plugin
- Added LoaderConfig plugin; allows setting of loader priorities for
various MIME types

Ver 4.1.0 (2022-06-30)
======================
Expand Down
1 change: 1 addition & 0 deletions doc/manual/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Global plugins
plugins_global/command
plugins_global/saveimage
plugins_global/downloads
plugins_global/loaderconfig


.. _sec-localplugins:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions doc/manual/plugins_global/loaderconfig.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. _sec-plugins-LoaderConfig:

LoaderConfig
============

.. image:: figures/loaderconfig-plugin.png
:align: center
:width: 800px
:alt: LoaderConfig plugin

.. automodapi:: ginga.rv.plugins.LoaderConfig
:no-heading:
:skip: LoaderConfig
25 changes: 24 additions & 1 deletion ginga/rv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from ginga.misc import Task, ModuleManager, Settings, log
import ginga.version as version
import ginga.toolkit as ginga_toolkit
from ginga.util import paths, rgb_cms, json, compat
from ginga.util import paths, rgb_cms, json, compat, loader

# Catch warnings
logging.captureWarnings(True)
Expand Down Expand Up @@ -168,6 +168,9 @@
menu="Header [G]", hidden=False, category='Utils', ptype='global'),
Bunch(module='Zoom', tab='Zoom', workspace='left', start=False,
menu="Zoom [G]", category='Utils', ptype='global'),
Bunch(module='LoaderConfig', tab='Loaders', workspace='channels',
start=False, menu="LoaderConfig [G]", category='Debug',
ptype='global'),
]


Expand Down Expand Up @@ -518,6 +521,26 @@ def main(self, options, args):
guiHdlr.setFormatter(fmt)
logger.addHandler(guiHdlr)

# Set loader priorities, if user has saved any
# (see LoaderConfig plugin)
path = os.path.join(self.basedir, 'loaders.json')
if os.path.exists(path):
try:
with open(path, 'r') as in_f:
loader_dct = json.loads(in_f.read())

# set saved priorities for openers
for mimetype, m_dct in loader_dct.items():
for name, l_dct in m_dct.items():
opener = loader.get_opener(name)
loader.add_opener(opener, [mimetype],
priority=l_dct['priority'],
note=opener.__doc__)

except Exception as e:
logger.error(f"failed to process loader file '{path}': {e}",
exc_info=True)

# Load any custom modules
if options.modules is not None:
modules = options.modules.split(',')
Expand Down
173 changes: 173 additions & 0 deletions ginga/rv/plugins/LoaderConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# This is open-source software licensed under a BSD license.
# Please see the file LICENSE.txt for details.
"""
The ``LoaderConfig`` plugin allows you to configure the file openers that
can be used to load various content into Ginga.

Registered file openers are associated with file MIME types, and there can
be several openers for a single MIME type. A priority associated
with a MIME type/opener pairing determines which opener will be used
for each type--the lowest priority value will determine which opener will
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems counter-intuitive to me. Generally speaking, I thought highest priority means it would come first?

It sounds like you are using ascending order number, which is kind of like reverse of the priority?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it depends on how you interpret a priority number. There are systems for where a lower number has higher priority (e.g. Unix process priorities). And it does match the typical English usage, to wit: "my first priority is ...; my second priority is ...".

I think as long as the documentation is clear on this (see conspicuous note in plugin docstring) there should be no misunderstanding.

be used. If there are more than one opener with the same low priority
then the user will be prompted for which opener to use, when opening a
file in Ginga. This plugin can be used to set the opener preferences
and save it to the user's $HOME/.ginga configuration area.
Comment on lines +13 to +14
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to give an example exactly what the filename should be in $HOME/.ginga and some example content of that file.


**Plugin Type: Global**

``LoaderConfig`` is a global plugin. Only one instance can be opened.

**Usage**

After starting the plugin, the display will show all the registered MIME
types and the openers registered for those types, with an associated
priority for each MIME type/opener pairing.

Select one or more lines and type a priority for them in the box labeled
"Priority:"; press "Set" (or ENTER) to set the priority of those items.

.. note:: The lower the number, the higher the priority. Negative numbers
are fine and the default priority for a loader is usually 0.
So, for example, if there are two loaders available for a MIME
type and one priority is set to -1 and the other to 0, the one
with -1 will be used without asking the user to choose.


Click "Save" to save the priorities to $HOME/.ginga/loaders.json so that
they will be reloaded and used on subsequent restarts of the program.
"""
import os.path

from ginga import GingaPlugin
from ginga.util.paths import ginga_home
from ginga.util import loader, json
from ginga.gw import Widgets

__all__ = ['LoaderConfig']


class LoaderConfig(GingaPlugin.GlobalPlugin):

def __init__(self, fv):
super().__init__(fv)

self.loader_dct = dict()

self.columns = [("Name", 'name'),
("Priority", 'priority'),
("Note", 'note')]

self.gui_up = False

def build_gui(self, container):
vbox = Widgets.VBox()
vbox.set_spacing(1)

tv = Widgets.TreeView(sortable=True, use_alt_row_color=True,
selection='multiple', auto_expand=True)
tv.add_callback('selected', self.select_cb)
tv.setup_table(self.columns, 2, 'name')
self.w.loader_tbl = tv

vbox.add_widget(tv, stretch=1)

tbar = Widgets.HBox()
tbar.set_border_width(4)
tbar.set_spacing(4)

tbar.add_widget(Widgets.Label('Priority:'))
pri = Widgets.TextEntrySet(editable=True)
pri.set_tooltip("Edit priority of loader (lower=better, negative numbers ok)")
pri.set_enabled(False)
pri.add_callback('activated', self.set_priority_cb)
self.w.pri_edit = pri
tbar.add_widget(pri)

tbar.add_widget(Widgets.Label(''), stretch=1)
vbox.add_widget(tbar, stretch=0)

btns = Widgets.HBox()
btns.set_border_width(4)
btns.set_spacing(4)

btn = Widgets.Button("Close")
btn.add_callback('activated', lambda w: self.close())
btns.add_widget(btn)
btn = Widgets.Button("Help")
btn.add_callback('activated', lambda w: self.help())
btns.add_widget(btn, stretch=0)
btn = Widgets.Button("Save")
btn.add_callback('activated', lambda w: self.save_loaders_cb())
btn.set_tooltip("Save configuration of loaders")
btns.add_widget(btn, stretch=0)

btns.add_widget(Widgets.Label(''), stretch=1)
vbox.add_widget(btns, stretch=0)
container.add_widget(vbox, stretch=1)

self.gui_up = True

def start(self):
# create loader table
tree_dct = dict()
for mimetype, dct in loader.loader_by_mimetype.items():
md = dict()
tree_dct[mimetype] = md
for name, bnch in dct.items():
md[name] = dict(name=bnch.opener.name,
priority=bnch.priority,
note=bnch.opener.note)
self.loader_dct = tree_dct

self.w.loader_tbl.set_tree(self.loader_dct)
self.w.loader_tbl.set_optimal_column_widths()

def stop(self):
self.gui_up = False

def set_priority_cb(self, w):
sel_dct = self.w.loader_tbl.get_selected()
priority = int(self.w.pri_edit.get_text())
for mimetype, ld_dct in sel_dct.items():
for name, m_dct in ld_dct.items():
self.loader_dct[mimetype][name]['priority'] = priority
# actually change it in the loader registration
opener = loader.get_opener(name)
loader.add_opener(opener, [mimetype], priority=priority,
note=opener.__doc__)
# update the UI table
self.w.loader_tbl.set_tree(self.loader_dct)

def save_loaders_cb(self):
path = os.path.join(ginga_home, 'loaders.json')
try:
with open(path, 'w') as out_f:
out_f.write(json.dumps(self.loader_dct, indent=4))

except Exception as e:
self.logger.error(f"failed to save loader file: {e}",
exc_info=True)
self.fv.show_error(str(e))

def select_cb(self, w, dct):
selected = len(dct) > 0
self.w.pri_edit.set_enabled(selected)
if not selected:
self.w.pri_edit.set_text('')
else:
# only set priority widget if all selected are the same
# unique value
priorities = set([l_dct['priority']
for m_dct in dct.values()
for l_dct in m_dct.values()])
if len(priorities) == 1:
self.w.pri_edit.set_text(str(priorities.pop()))
else:
self.w.pri_edit.set_text('')

def close(self):
self.fv.stop_global_plugin(str(self))
return True

def __str__(self):
return 'loaderconfig'
4 changes: 2 additions & 2 deletions ginga/util/io/io_fits.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class AstropyFitsFileHandler(BaseFitsFileHandler):
"""

name = 'astropy.io.fits'
mimetypes = ['image/fits', 'image/x-fits']
mimetypes = ['image/fits', 'image/x-fits', 'application/fits']

@classmethod
def check_availability(cls):
Expand Down Expand Up @@ -562,7 +562,7 @@ class FitsioFileHandler(BaseFitsFileHandler):
"""For loading FITS (Flexible Image Transport System) data files.
"""
name = 'fitsio'
mimetypes = ['image/fits', 'image/x-fits']
mimetypes = ['image/fits', 'image/x-fits', 'application/fits']

@classmethod
def check_availability(cls):
Expand Down