From 2bfd38aadfb1ff6515fb17953e1bf0870f92d2a1 Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Wed, 18 Dec 2019 14:07:01 -1000 Subject: [PATCH 1/9] Add Reproject plugin for reference viewer - This is a plugin that basically provides a GUI for doing WCS reprojections using the AstroPy "reproject" module. - Provided mostly as a basis for further discussion on #328 --- ginga/rv/plugins/Reproject.py | 251 ++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 ginga/rv/plugins/Reproject.py diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py new file mode 100644 index 000000000..26e24c39f --- /dev/null +++ b/ginga/rv/plugins/Reproject.py @@ -0,0 +1,251 @@ +# This is open-source software licensed under a BSD license. +# Please see the file LICENSE.txt for details. +""" +``Reproject`` is a simple plugin to reproject an image from one WCS to +another. + +**Plugin Type: Local** + +``Reproject`` is a local plugin, which means it is associated with a channel. +An instance can be opened for each channel. + +**Usage** + +Start the plugin on a channel. Load the image which has the WCS that you +want to use for the reprojection into the channel. Click "Set WCS" to save +the WCS; you will see the image copied into the plugin viewer and the +message "WCS set" will briefly appear there. + +Now load any image that you want to reproject into the channel. Click +"Reproject" to reproject the image using the saved image and it's header/WCS +to do so. The reprojected image will appear in the channel as a separate +image. You can keep loading images and reprojecting them. If you want to +do a different reprojection, simply repeat the "Set WCS", "Reproject" +sequence at any time. + +The parameters for the reprojection can be set in the GUI controls. +""" +import os.path +import numpy as np +from astropy.io import fits +import reproject + +from ginga import GingaPlugin, AstroImage +from ginga.gw import Widgets, Viewers + +__all__ = ['Reproject'] + + +_choose = {'adaptive': dict(order=['nearest-neighbor', 'bilinear'], + method=reproject.reproject_adaptive), + 'interp': dict(order=['nearest-neighbor', 'bilinear', + 'biquadratic', 'bicubic'], + method=reproject.reproject_interp), + 'exact': dict(order=['n/a'], + method=reproject.reproject_exact), + } + + +class Reproject(GingaPlugin.LocalPlugin): + + def __init__(self, fv, fitsimage): + # superclass defines some variables for us, like logger + super(Reproject, self).__init__(fv, fitsimage) + + self._wd = 400 + self._ht = 300 + _sz = max(self._wd, self._ht) + # hack to set a reasonable starting position for the splitter + self._split_sizes = [_sz, _sz] + + self.count = 1 + self.cache_dir = "/tmp" + self.out_wcs = None + self._proj_types = list(_choose.keys()) + self._proj_types.sort() + self._proj_type = self._proj_types[0] + + def build_gui(self, container): + vtop = Widgets.VBox() + vtop.set_border_width(4) + + box, sw, orientation = Widgets.get_oriented_box(container) + # Uncomment to debug; passing parent logger generates too + # much noise in the main logger + zi = Viewers.CanvasView(logger=self.logger) + zi.set_desired_size(self._wd, self._ht) + zi.enable_autozoom('override') + zi.enable_autocuts('override') + zi.set_bg(0.4, 0.4, 0.4) + zi.show_pan_mark(True) + # for debugging + zi.set_name('reproject-image') + self.rpt_image = zi + + bd = zi.get_bindings() + bd.enable_all(True) + + iw = Viewers.GingaViewerWidget(zi) + iw.resize(self._wd, self._ht) + paned = Widgets.Splitter(orientation=orientation) + paned.add_widget(iw) + self.w.splitter = paned + + vbox2 = Widgets.VBox() + captions = (("Reproject", 'button', "Set WCS", 'button'), + ) + w, b = Widgets.build_info(captions, orientation=orientation) + self.w.update(b) + vbox2.add_widget(w, stretch=0) + + b.reproject.add_callback('activated', self.reproject_cb) + b.reproject.set_tooltip("Click to save channel image as reprojection WCS") + b.set_wcs.add_callback('activated', self.set_wcs_cb) + b.set_wcs.set_tooltip("Click to reproject channel image using saved WCS") + + captions = (("Reproject type", 'combobox'), + ("Order", 'combobox'), + ) + w, b = Widgets.build_info(captions, orientation=orientation) + self.w.update(b) + vbox2.add_widget(w, stretch=0) + + cb = b.reproject_type + for name in self._proj_types: + cb.insert_alpha(name) + cb.set_tooltip("Set type of reprojection") + cb.add_callback('activated', self.set_reprojection_cb) + idx = self._proj_types.index(self._proj_type) + cb.set_index(idx) + + self._adjust_orders() + + # stretch + spacer = Widgets.Label('') + vbox2.add_widget(spacer, stretch=1) + + box.add_widget(vbox2, stretch=1) + + paned.add_widget(sw) + paned.set_sizes(self._split_sizes) + + vtop.add_widget(paned, stretch=5) + + 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) + btns.add_widget(Widgets.Label(''), stretch=1) + vtop.add_widget(btns, stretch=0) + + container.add_widget(vtop, stretch=5) + self.gui_up = True + + def close(self): + self.fv.stop_local_plugin(self.chname, str(self)) + return True + + def stop(self): + self._split_sizes = self.w.splitter.get_sizes() + + def redo(self): + pass + + def reproject(self, image, name=None, shape=None, cache_dir=None): + if image is None or image.wcs is None: + self.fv.show_error("Reproject: null target image or WCS") + return + wcs_in = image.wcs.wcs + data_in = image.get_data() + if shape is None: + shape = image.shape + + proj_out = self.wcs_out + + method = _choose[self._proj_type]['method'] + + kwargs = dict(return_footprint=True, shape_out=shape) + order = self.w.order.get_text() + if order != 'n/a': + kwargs['order'] = order + + # do reprojection + try: + data_out, mask = method((data_in, wcs_in), proj_out, + **kwargs) + + except Exception as e: + self.fv.show_error("reproject error: {}".format(e)) + return None + + # TODO: use mask (probably as alpha mask) + hdu = fits.PrimaryHDU(data_out) + if name is None: + name = self.get_name(image.get('name'), cache_dir) + + # Write image to cache directory, if one is defined + path = None + if cache_dir is not None: + path = os.path.join(cache_dir, name + '.fits') + hdulst = fits.HDUList([hdu]) + hdulst.writeto(path) + self.logger.info("wrote {}".format(path)) + + # TODO: decent header with WCS + img_out = AstroImage.AstroImage(logger=self.logger) + img_out.load_hdu(hdu) + img_out.set(name=name, path=path) + + return img_out + + def set_wcs_cb(self, w): + image = self.fitsimage.get_image() + if image is None or image.wcs is None: + return + + self.rpt_image.set_image(image) + #self.wcs_out = image.wcs.wcs + header = image.get_header() + self.wcs_out = fits.Header(header) + self.rpt_image.onscreen_message("WCS set", delay=1.0) + + def reproject_cb(self, w): + image = self.fitsimage.get_image() + img_out = self.reproject(image, cache_dir=self.cache_dir) + + if img_out is not None: + self.channel.add_image(img_out) + + def get_name(self, name_in, cache_dir): + found = False + while not found: + name = name_in + '-rpjt-{}'.format(self.count) + self.count += 1 + + # Write image to cache directory, if one is defined + path = None + if cache_dir is None: + return name + + path = os.path.join(cache_dir, name + '.fits') + if not os.path.exists(path): + return name + + def set_reprojection_cb(self, w, idx): + self._proj_type = w.get_text() + self._adjust_orders() + + def _adjust_orders(self): + order = _choose[self._proj_type]['order'] + self.w.order.clear() + for name in order: + self.w.order.insert_alpha(name) + + def __str__(self): + return 'reproject' From 2a21d3d513eab1a9ca1c11ddb102ab0fda173ecc Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Wed, 18 Dec 2019 14:13:35 -1000 Subject: [PATCH 2/9] Added to plugins menu under "Utils" --- ginga/rv/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ginga/rv/main.py b/ginga/rv/main.py index d652ed75f..370781851 100644 --- a/ginga/rv/main.py +++ b/ginga/rv/main.py @@ -153,6 +153,7 @@ menu="History [G]", start=False, category='Utils', ptype='global'), Bunch(module='Mosaic', workspace='dialogs', category='Utils', ptype='local'), Bunch(module='Collage', workspace='dialogs', category='Utils', ptype='local'), + Bunch(module='Reproject', workspace='dialogs', category='Utils', ptype='local'), Bunch(module='FBrowser', tab='Open File', workspace='right', menu="Open File [G]", start=False, category='Utils', ptype='global'), Bunch(module='Preferences', workspace='dialogs', category='Utils', From 73315ae9a2788d08b46d8ea21251eb57e9751337 Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Wed, 18 Dec 2019 17:51:24 -1000 Subject: [PATCH 3/9] Added WCS to the reprojected image --- ginga/rv/plugins/Reproject.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py index 26e24c39f..56f7c52f6 100644 --- a/ginga/rv/plugins/Reproject.py +++ b/ginga/rv/plugins/Reproject.py @@ -27,6 +27,7 @@ """ import os.path import numpy as np +#np.set_printoptions(threshold=np.inf) from astropy.io import fits import reproject @@ -152,6 +153,7 @@ def close(self): return True def stop(self): + self.img_out = None self._split_sizes = self.w.splitter.get_sizes() def redo(self): @@ -166,7 +168,8 @@ def reproject(self, image, name=None, shape=None, cache_dir=None): if shape is None: shape = image.shape - proj_out = self.wcs_out + header = self.img_out.get_header() + proj_out = fits.Header(header) method = _choose[self._proj_type]['method'] @@ -186,6 +189,8 @@ def reproject(self, image, name=None, shape=None, cache_dir=None): # TODO: use mask (probably as alpha mask) hdu = fits.PrimaryHDU(data_out) + # add conversion wcs keywords + hdu.header.update(self.img_out.wcs.wcs.to_header()) if name is None: name = self.get_name(image.get('name'), cache_dir) @@ -197,7 +202,6 @@ def reproject(self, image, name=None, shape=None, cache_dir=None): hdulst.writeto(path) self.logger.info("wrote {}".format(path)) - # TODO: decent header with WCS img_out = AstroImage.AstroImage(logger=self.logger) img_out.load_hdu(hdu) img_out.set(name=name, path=path) @@ -210,9 +214,7 @@ def set_wcs_cb(self, w): return self.rpt_image.set_image(image) - #self.wcs_out = image.wcs.wcs - header = image.get_header() - self.wcs_out = fits.Header(header) + self.img_out = image self.rpt_image.onscreen_message("WCS set", delay=1.0) def reproject_cb(self, w): From e90a5737582c1e93481bc895000ac00cea52315b Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Wed, 8 Jan 2020 09:05:21 -1000 Subject: [PATCH 4/9] Leave viewer responsive while reprojecting --- ginga/rv/plugins/Reproject.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py index 56f7c52f6..666da0308 100644 --- a/ginga/rv/plugins/Reproject.py +++ b/ginga/rv/plugins/Reproject.py @@ -32,6 +32,7 @@ import reproject from ginga import GingaPlugin, AstroImage +from ginga.misc import Future from ginga.gw import Widgets, Viewers __all__ = ['Reproject'] @@ -106,6 +107,7 @@ def build_gui(self, container): captions = (("Reproject type", 'combobox'), ("Order", 'combobox'), + ("status", 'llabel'), ) w, b = Widgets.build_info(captions, orientation=orientation) self.w.update(b) @@ -179,13 +181,7 @@ def reproject(self, image, name=None, shape=None, cache_dir=None): kwargs['order'] = order # do reprojection - try: - data_out, mask = method((data_in, wcs_in), proj_out, - **kwargs) - - except Exception as e: - self.fv.show_error("reproject error: {}".format(e)) - return None + data_out, mask = method((data_in, wcs_in), proj_out, **kwargs) # TODO: use mask (probably as alpha mask) hdu = fits.PrimaryHDU(data_out) @@ -217,12 +213,27 @@ def set_wcs_cb(self, w): self.img_out = image self.rpt_image.onscreen_message("WCS set", delay=1.0) + def _reproject_cont(self, future): + self.fv.gui_call(self.w.status.set_text, "") + try: + img_out = future.get_value() + + except Exception as e: + self.fv.show_error("reproject error: {}".format(e)) + return + + if img_out is not None: + self.fv.gui_do(self.channel.add_image, img_out) + def reproject_cb(self, w): image = self.fitsimage.get_image() - img_out = self.reproject(image, cache_dir=self.cache_dir) - if img_out is not None: - self.channel.add_image(img_out) + future = Future.Future() + future.freeze(self.reproject, image, cache_dir=self.cache_dir) + future.add_callback('resolved', self._reproject_cont) + + self.w.status.set_text("Working...") + self.fv.nongui_do_future(future) def get_name(self, name_in, cache_dir): found = False From 8383e68716ca9775db73e053394c79817e587555 Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Wed, 8 Jan 2020 09:27:33 -1000 Subject: [PATCH 5/9] Quiet flake8 warning --- ginga/rv/plugins/Reproject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py index 666da0308..b539a08d2 100644 --- a/ginga/rv/plugins/Reproject.py +++ b/ginga/rv/plugins/Reproject.py @@ -26,7 +26,7 @@ The parameters for the reprojection can be set in the GUI controls. """ import os.path -import numpy as np +#import numpy as np #np.set_printoptions(threshold=np.inf) from astropy.io import fits import reproject From bc363273fc3827aba6a0da8be61a576cdae0bddd Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Thu, 9 Jan 2020 17:32:36 -1000 Subject: [PATCH 6/9] Create larger output projection so can store rotated result --- ginga/rv/plugins/Reproject.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py index b539a08d2..6815f1d58 100644 --- a/ginga/rv/plugins/Reproject.py +++ b/ginga/rv/plugins/Reproject.py @@ -26,7 +26,7 @@ The parameters for the reprojection can be set in the GUI controls. """ import os.path -#import numpy as np +import numpy as np #np.set_printoptions(threshold=np.inf) from astropy.io import fits import reproject @@ -34,6 +34,7 @@ from ginga import GingaPlugin, AstroImage from ginga.misc import Future from ginga.gw import Widgets, Viewers +from ginga.util import wcs __all__ = ['Reproject'] @@ -113,6 +114,8 @@ def build_gui(self, container): self.w.update(b) vbox2.add_widget(w, stretch=0) + b.status.set_text("") + cb = b.reproject_type for name in self._proj_types: cb.insert_alpha(name) @@ -161,21 +164,36 @@ def stop(self): def redo(self): pass - def reproject(self, image, name=None, shape=None, cache_dir=None): + def reproject(self, image, name=None, cache_dir=None): if image is None or image.wcs is None: self.fv.show_error("Reproject: null target image or WCS") return wcs_in = image.wcs.wcs data_in = image.get_data() - if shape is None: - shape = image.shape + hdr_in = image.get_header() header = self.img_out.get_header() + + ((_xr, _yr), + (cdelt1, cdelt2)) = wcs.get_xy_rotation_and_scale(hdr_in) + + # preserve transformed image's pixel scale + header['CDELT1'] = cdelt1 + header['CDELT2'] = cdelt2 + + # create shape big enough to handle rotation + ht, wd = data_in.shape[:2] + side = int(np.ceil(np.sqrt(wd ** 2 + ht ** 2))) + header['NAXIS1'] = side + header['NAXIS2'] = side + header['CRPIX1'] = side / 2 + header['CRPIX2'] = side / 2 + proj_out = fits.Header(header) method = _choose[self._proj_type]['method'] - kwargs = dict(return_footprint=True, shape_out=shape) + kwargs = dict(return_footprint=True) order = self.w.order.get_text() if order != 'n/a': kwargs['order'] = order From 9123dd268ba85f9e29bdfde026e91ba6d0edf06d Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Fri, 10 Jan 2020 14:30:39 -1000 Subject: [PATCH 7/9] Add Undistort buttin - added an undistort button, which creates a reprojection based on the current WCS with SIP information removed - added enable/disable code to grey out buttons that can't be used --- ginga/rv/plugins/Reproject.py | 107 +++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 29 deletions(-) diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py index 6815f1d58..82fc99a73 100644 --- a/ginga/rv/plugins/Reproject.py +++ b/ginga/rv/plugins/Reproject.py @@ -12,9 +12,12 @@ **Usage** Start the plugin on a channel. Load the image which has the WCS that you -want to use for the reprojection into the channel. Click "Set WCS" to save -the WCS; you will see the image copied into the plugin viewer and the -message "WCS set" will briefly appear there. +want to use for the reprojection into the channel. + +**Reprojection** + +Click "Set WCS" to save the WCS; you will see the image copied into the +plugin viewer and the message "WCS set" will briefly appear there. Now load any image that you want to reproject into the channel. Click "Reproject" to reproject the image using the saved image and it's header/WCS @@ -24,8 +27,19 @@ sequence at any time. The parameters for the reprojection can be set in the GUI controls. + +**Undistort** + +Clicking the "Undistort" button will attempt to "undistort" the image by +doing a reprojection based on the current image's WCS with SIP distortion +information removed. + +In this case there is no need to click "Set WCS". The WCS from the channel +image will be used to create the reprojection WCS. To use this feature the +WCS must contain SIP distortion information. """ import os.path +import copy import numpy as np #np.set_printoptions(threshold=np.inf) from astropy.io import fits @@ -63,10 +77,10 @@ def __init__(self, fv, fitsimage): self.count = 1 self.cache_dir = "/tmp" - self.out_wcs = None + self.img_out = None self._proj_types = list(_choose.keys()) self._proj_types.sort() - self._proj_type = self._proj_types[0] + self._proj_type = 'interp' def build_gui(self, container): vtop = Widgets.VBox() @@ -96,15 +110,20 @@ def build_gui(self, container): vbox2 = Widgets.VBox() captions = (("Reproject", 'button', "Set WCS", 'button'), + ("Undistort", 'button'), ) w, b = Widgets.build_info(captions, orientation=orientation) self.w.update(b) vbox2.add_widget(w, stretch=0) - b.reproject.add_callback('activated', self.reproject_cb) + b.reproject.add_callback('activated', self.reproject_cb, False) b.reproject.set_tooltip("Click to save channel image as reprojection WCS") + b.reproject.set_enabled(False) b.set_wcs.add_callback('activated', self.set_wcs_cb) b.set_wcs.set_tooltip("Click to reproject channel image using saved WCS") + b.undistort.add_callback('activated', self.reproject_cb, True) + b.undistort.set_tooltip("Click to correct SIP distortion") + #b.undistort.set_enabled(False) captions = (("Reproject type", 'combobox'), ("Order", 'combobox'), @@ -157,43 +176,65 @@ def close(self): self.fv.stop_local_plugin(self.chname, str(self)) return True + def start(self): + self.redo() + def stop(self): self.img_out = None self._split_sizes = self.w.splitter.get_sizes() + self.gui_up = False def redo(self): - pass - - def reproject(self, image, name=None, cache_dir=None): + if not self.gui_up: + return + image = self.fitsimage.get_image() + if image is None or image.wcs is None or image.wcs.wcs is None: + self.w.set_wcs.set_enabled(False) + self.w.reproject.set_enabled(False) + self.w.undistort.set_enabled(False) + else: + self.w.set_wcs.set_enabled(True) + self.w.reproject.set_enabled(self.img_out is not None) + self.w.undistort.set_enabled(image.wcs.wcs.sip is not None) + + def reproject(self, image, name=None, undistort_sip=False, cache_dir=None): if image is None or image.wcs is None: self.fv.show_error("Reproject: null target image or WCS") return wcs_in = image.wcs.wcs data_in = image.get_data() - hdr_in = image.get_header() - header = self.img_out.get_header() + if undistort_sip: + proj_out = copy.deepcopy(wcs_in) + proj_out.sip = None + shape = data_in.shape + + else: + hdr_in = image.get_header() - ((_xr, _yr), - (cdelt1, cdelt2)) = wcs.get_xy_rotation_and_scale(hdr_in) + header = self.img_out.get_header() - # preserve transformed image's pixel scale - header['CDELT1'] = cdelt1 - header['CDELT2'] = cdelt2 + ((_xr, _yr), + (cdelt1, cdelt2)) = wcs.get_xy_rotation_and_scale(hdr_in) - # create shape big enough to handle rotation - ht, wd = data_in.shape[:2] - side = int(np.ceil(np.sqrt(wd ** 2 + ht ** 2))) - header['NAXIS1'] = side - header['NAXIS2'] = side - header['CRPIX1'] = side / 2 - header['CRPIX2'] = side / 2 + # preserve transformed image's pixel scale + header['CDELT1'] = cdelt1 + header['CDELT2'] = cdelt2 - proj_out = fits.Header(header) + # create shape big enough to handle rotation + ht, wd = data_in.shape[:2] + side = int(np.ceil(np.sqrt(wd ** 2 + ht ** 2))) + header['NAXIS1'] = side + header['NAXIS2'] = side + header['CRPIX1'] = side / 2 + header['CRPIX2'] = side / 2 + + proj_out = fits.Header(header) + shape = (side, side) method = _choose[self._proj_type]['method'] - kwargs = dict(return_footprint=True) + kwargs = dict(return_footprint=True, shape_out=shape) order = self.w.order.get_text() if order != 'n/a': kwargs['order'] = order @@ -203,8 +244,14 @@ def reproject(self, image, name=None, cache_dir=None): # TODO: use mask (probably as alpha mask) hdu = fits.PrimaryHDU(data_out) - # add conversion wcs keywords - hdu.header.update(self.img_out.wcs.wcs.to_header()) + + # Add WCS to the new image + # TODO: carry over other keywords besides WCS? + if undistort_sip: + hdu.header.update(proj_out.to_header()) + else: + hdu.header.update(self.img_out.wcs.wcs.to_header()) + if name is None: name = self.get_name(image.get('name'), cache_dir) @@ -229,6 +276,7 @@ def set_wcs_cb(self, w): self.rpt_image.set_image(image) self.img_out = image + self.w.reproject.set_enabled(True) self.rpt_image.onscreen_message("WCS set", delay=1.0) def _reproject_cont(self, future): @@ -243,11 +291,12 @@ def _reproject_cont(self, future): if img_out is not None: self.fv.gui_do(self.channel.add_image, img_out) - def reproject_cb(self, w): + def reproject_cb(self, w, undistort_sip): image = self.fitsimage.get_image() future = Future.Future() - future.freeze(self.reproject, image, cache_dir=self.cache_dir) + future.freeze(self.reproject, image, cache_dir=self.cache_dir, + undistort_sip=undistort_sip) future.add_callback('resolved', self._reproject_cont) self.w.status.set_text("Working...") From 1e15cde272b975c5a987228becfe5ec328c97922 Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Fri, 10 Jan 2020 16:13:21 -1000 Subject: [PATCH 8/9] Added a readable error when reproject module not installed - added a readable error message when trying to start the plugin without having the astropy reproject module installed --- ginga/rv/plugins/Reproject.py | 36 ++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py index 82fc99a73..3b34eb142 100644 --- a/ginga/rv/plugins/Reproject.py +++ b/ginga/rv/plugins/Reproject.py @@ -7,7 +7,8 @@ **Plugin Type: Local** ``Reproject`` is a local plugin, which means it is associated with a channel. -An instance can be opened for each channel. +An instance can be opened for each channel. You need to have the Astropy +'reproject' module installed to use this plugin. **Usage** @@ -43,7 +44,13 @@ import numpy as np #np.set_printoptions(threshold=np.inf) from astropy.io import fits -import reproject + +have_reproject = False +try: + import reproject + have_reproject = True +except ImportError: + pass from ginga import GingaPlugin, AstroImage from ginga.misc import Future @@ -52,15 +59,17 @@ __all__ = ['Reproject'] - -_choose = {'adaptive': dict(order=['nearest-neighbor', 'bilinear'], - method=reproject.reproject_adaptive), - 'interp': dict(order=['nearest-neighbor', 'bilinear', - 'biquadratic', 'bicubic'], - method=reproject.reproject_interp), - 'exact': dict(order=['n/a'], - method=reproject.reproject_exact), - } +if have_reproject: + _choose = {'adaptive': dict(order=['nearest-neighbor', 'bilinear'], + method=reproject.reproject_adaptive), + 'interp': dict(order=['nearest-neighbor', 'bilinear', + 'biquadratic', 'bicubic'], + method=reproject.reproject_interp), + 'exact': dict(order=['n/a'], + method=reproject.reproject_exact), + } +else: + _choose = {} class Reproject(GingaPlugin.LocalPlugin): @@ -83,12 +92,13 @@ def __init__(self, fv, fitsimage): self._proj_type = 'interp' def build_gui(self, container): + if not have_reproject: + raise Exception("Please install the 'reproject' module to use " + "this plugin") vtop = Widgets.VBox() vtop.set_border_width(4) box, sw, orientation = Widgets.get_oriented_box(container) - # Uncomment to debug; passing parent logger generates too - # much noise in the main logger zi = Viewers.CanvasView(logger=self.logger) zi.set_desired_size(self._wd, self._ht) zi.enable_autozoom('override') From e75d6d494f53881d006205b9756689b7d4389b7c Mon Sep 17 00:00:00 2001 From: Eric Jeschke Date: Thu, 5 May 2022 15:47:28 -1000 Subject: [PATCH 9/9] Fix bandit check on use of /tmp --- ginga/rv/plugins/Reproject.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ginga/rv/plugins/Reproject.py b/ginga/rv/plugins/Reproject.py index 3b34eb142..c18fa081c 100644 --- a/ginga/rv/plugins/Reproject.py +++ b/ginga/rv/plugins/Reproject.py @@ -40,6 +40,7 @@ WCS must contain SIP distortion information. """ import os.path +import tempfile import copy import numpy as np #np.set_printoptions(threshold=np.inf) @@ -85,7 +86,7 @@ def __init__(self, fv, fitsimage): self._split_sizes = [_sz, _sz] self.count = 1 - self.cache_dir = "/tmp" + self.cache_dir = tempfile.gettempdir() self.img_out = None self._proj_types = list(_choose.keys()) self._proj_types.sort()