#
# Copyright (C) 2007, 2016, 2021, 2024
# Smithsonian Astrophysical Observatory
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""Support image display with an external display tool.
At present the only supported application is DS9 [DS9]_, which is
connected to via XPA [XPA]_.
References
----------
.. [DS9] SAOImageDS9, "An image display and visualization tool for astronomical data", https://ds9.si.edu/
.. [XPA] "The XPA Messaging System", https://github.com/ericmandel/xpa
"""
import numpy
from sherpa.utils import NoNewAttributesAfterInit, bool_cast
import logging
warning = logging.getLogger(__name__).warning
backend = None
try:
from . import ds9_backend as backend
except Exception as e:
# if DS9 is not found for some reason, like inside gdb
# give a useful warning and fall back on dummy_backend of noops
warning("imaging routines will not be available, \n" +
"failed to import sherpa.image.ds9_backend due to \n'%s: %s'" %
(type(e).__name__, str(e)))
from . import dummy_backend as backend
__all__ = ('Image', 'DataImage', 'ModelImage', 'RatioImage',
'ResidImage', 'PSFImage', 'PSFKernelImage', 'SourceImage',
'ComponentModelImage', 'ComponentSourceImage')
[docs]
class Image(NoNewAttributesAfterInit):
"""Base class for sending image data to an external viewer."""
def __init__(self):
NoNewAttributesAfterInit.__init__(self)
[docs]
def close():
"""Stop the image viewer."""
backend.close()
close = staticmethod(close)
[docs]
def delete_frames():
"""Delete all the frames open in the image viewer."""
backend.delete_frames()
delete_frames = staticmethod(delete_frames)
[docs]
def get_region(coord):
"""Return the region defined in the image viewer.
Parameters
----------
coord : str
The name of the coordinate system (the empty string means
to use the current system).
Returns
-------
region : str
The region, or regions, or the empty string.
"""
return backend.get_region(coord)
get_region = staticmethod(get_region)
[docs]
def image(self, array, shape=None, newframe=False, tile=False):
newframe = bool_cast(newframe)
tile = bool_cast(tile)
if shape is None:
backend.image(array, newframe, tile)
else:
backend.image(array.reshape(shape), newframe, tile)
[docs]
def open():
"""Start the image viewer."""
backend.open()
open = staticmethod(open)
[docs]
def set_wcs(self, keys):
backend.wcs(keys)
[docs]
def set_region(reg, coord):
"""Set the region to display in the image viewer.
Parameters
----------
reg : str
The region to display.
coord : str
The name of the coordinate system (the empty string means
to use the current system).
"""
backend.set_region(reg, coord)
set_region = staticmethod(set_region)
[docs]
def xpaget(arg):
"""Return the result of an XPA call to the image viewer.
Send a query to the image viewer.
Parameters
----------
arg : str
A command to send to the image viewer via XPA.
Returns
-------
returnval : str
"""
return backend.xpaget(arg)
xpaget = staticmethod(xpaget)
[docs]
def xpaset(arg, data=None):
"""Return the result of an XPA call to the image viewer.
Send a command to the image viewer.
Parameters
----------
arg : str
A command to send to the image viewer via XPA.
data : optional
The data for the command.
"""
return backend.xpaset(arg, data=None)
xpaset = staticmethod(xpaset)
[docs]
class DataImage(Image):
"""Image data.
Attributes
----------
name : str
y : array_like
The image data (pixel values) as a 2D array.
eqpos :
Coordinate transform to the "world" system.
sky :
Coordinate transform to the "physical" system.
"""
def __init__(self):
self.y = None
self.eqpos = None
self.sky = None
self.name = 'Data'
Image.__init__(self)
def __str__(self):
y = self.y
if self.y is not None:
y = numpy.array2string(self.y, separator=',', precision=4,
suppress_small=False)
return (('name = %s\n' % self.name) +
('y = %s\n' % y) +
('eqpos = %s\n' % self.eqpos) +
('sky = %s\n' % self.sky))
[docs]
def prepare_image(self, data):
self.y = data.get_img()
self.eqpos = getattr(data, 'eqpos', None)
self.sky = getattr(data, 'sky', None)
header = getattr(data, 'header', None)
if header is not None:
obj = header.get('OBJECT')
if obj is not None:
self.name = str(obj).replace(" ", "_")
[docs]
def image(self, shape=None, newframe=False, tile=False):
Image.image(self, self.y, shape, newframe, tile)
Image.set_wcs(self, (self.eqpos, self.sky, self.name))
[docs]
class ModelImage(Image):
def __init__(self):
self.name = 'Model'
self.y = None
self.eqpos = None
self.sky = None
Image.__init__(self)
def __str__(self):
y = self.y
if self.y is not None:
y = numpy.array2string(self.y, separator=',', precision=4,
suppress_small=False)
return (('name = %s\n' % self.name) +
('y = %s\n' % y) +
('eqpos = %s\n' % self.eqpos) +
('sky = %s\n' % self.sky))
[docs]
def prepare_image(self, data, model):
self.y = data.get_img(model)
self.y = self.y[1]
self.eqpos = getattr(data, 'eqpos', None)
self.sky = getattr(data, 'sky', None)
[docs]
def image(self, shape=None, newframe=False, tile=False):
Image.image(self, self.y, shape, newframe, tile)
Image.set_wcs(self, (self.eqpos, self.sky, self.name))
[docs]
class SourceImage(ModelImage):
def __init__(self):
ModelImage.__init__(self)
self.name = 'Source'
[docs]
def prepare_image(self, data, model):
# self.y = data.get_img(model)
# self.y = self.y[1]
self.y = data.eval_model(model)
data._check_shape()
self.y = self.y.reshape(*data.shape)
self.eqpos = getattr(data, 'eqpos', None)
self.sky = getattr(data, 'sky', None)
[docs]
class RatioImage(Image):
def __init__(self):
self.name = 'Ratio'
self.y = None
self.eqpos = None
self.sky = None
Image.__init__(self)
def __str__(self):
y = self.y
if self.y is not None:
y = numpy.array2string(self.y, separator=',', precision=4,
suppress_small=False)
return (('name = %s\n' % self.name) +
('y = %s\n' % y) +
('eqpos = %s\n' % self.eqpos) +
('sky = %s\n' % self.sky))
def _calc_ratio(self, ylist):
data = numpy.array(ylist[0])
model = numpy.asarray(ylist[1])
bad = numpy.where(model == 0.0)
data[bad] = 0.0
model[bad] = 1.0
return (data / model)
[docs]
def prepare_image(self, data, model):
self.y = data.get_img(model)
self.y = self._calc_ratio(self.y)
self.eqpos = getattr(data, 'eqpos', None)
self.sky = getattr(data, 'sky', None)
[docs]
def image(self, shape=None, newframe=False, tile=False):
Image.image(self, self.y, shape, newframe, tile)
Image.set_wcs(self, (self.eqpos, self.sky, self.name))
[docs]
class ResidImage(Image):
def __init__(self):
self.name = 'Residual'
self.y = None
self.eqpos = None
self.sky = None
Image.__init__(self)
def __str__(self):
y = self.y
if self.y is not None:
y = numpy.array2string(self.y, separator=',', precision=4,
suppress_small=False)
return (('name = %s\n' % self.name) +
('y = %s\n' % y) +
('eqpos = %s\n' % self.eqpos) +
('sky = %s\n' % self.sky))
def _calc_resid(self, ylist):
return ylist[0] - ylist[1]
[docs]
def prepare_image(self, data, model):
self.y = data.get_img(model)
self.y = self._calc_resid(self.y)
self.eqpos = getattr(data, 'eqpos', None)
self.sky = getattr(data, 'sky', None)
[docs]
def image(self, shape=None, newframe=False, tile=False):
Image.image(self, self.y, shape, newframe, tile)
Image.set_wcs(self, (self.eqpos, self.sky, self.name))
[docs]
class PSFImage(DataImage):
[docs]
def prepare_image(self, psf, data=None):
psfdata = psf.get_kernel(data, False)
DataImage.prepare_image(self, psfdata)
self.name = psf.kernel.name
[docs]
class PSFKernelImage(DataImage):
[docs]
def prepare_image(self, psf, data=None):
psfdata = psf.get_kernel(data)
DataImage.prepare_image(self, psfdata)
self.name = 'PSF_Kernel'
[docs]
class ComponentSourceImage(ModelImage):
[docs]
def prepare_image(self, data, model):
ModelImage.prepare_image(self, data, model)
# self.name = "Source component '%s'" % model.name
self.name = "Source_component"
[docs]
class ComponentModelImage(ModelImage):
[docs]
def prepare_image(self, data, model):
ModelImage.prepare_image(self, data, model)
# self.name = "Model component '%s'" % model.name
self.name = "Model_component"