Skip to content

Commit

Permalink
Merge pull request #48 from mwcraig/consistent-pixel-offset
Browse files Browse the repository at this point in the history
Add pixel_coords_offset attribute
  • Loading branch information
mwcraig committed Jan 2, 2019
2 parents fa26729 + ab9dbd2 commit 6ffc040
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 34 deletions.
65 changes: 35 additions & 30 deletions astrowidgets/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ class ImageWidget(ipyw.VBox):
e.g., rotation and mosaic. If this is enabled and you
do not have ``opencv``, you will get a warning.
pixel_coords_offset : int, optional
An offset, typically either 0 or 1, to add/subtract to all
pixel values when going to/from the displayed image.
*In almost all situations the default value, ``0``, is the
correct value to use.*
"""

def __init__(self, logger=None, image_width=500, image_height=500,
use_opencv=True):
use_opencv=True, pixel_coords_offset=0):
super().__init__()

# TODO: Is this the best place for this?
Expand All @@ -66,6 +72,8 @@ def __init__(self, logger=None, image_width=500, image_height=500,
self._is_marking = False
self._click_center = False

self._pixel_offset = pixel_coords_offset

self._jup_img = ipyw.Image(format='jpeg')

# Set the image margin to over the widgets default of 2px on
Expand Down Expand Up @@ -144,6 +152,18 @@ def image_height(self, value):
self._jup_img.height = str(value)
self._viewer.set_window_size(self.image_width, self.image_height)

@property
def pixel_offset(self):
"""
An offset, typically either 0 or 1, to add/subtract to all
pixel values when going to/from the displayed image.
*In almost all situations the default value, ``0``, is the
correct value to use.*
This value cannot be modified after initialization.
"""
return self._pixel_offset

def _mouse_move_cb(self, viewer, button, data_x, data_y):
"""
Callback to display position in RA/DEC deg.
Expand All @@ -157,11 +177,12 @@ def _mouse_move_cb(self, viewer, button, data_x, data_y):
iy = int(data_y + 0.5)
try:
imval = viewer.get_data(ix, iy)
imval = '{:8.3f}'.format(imval)
except Exception:
imval = 'N/A'

# Same as setting pixel_coords_offset=1 in general.cfg
val = 'X: {:.2f}, Y:{:.2f}'.format(data_x + 1, data_y + 1)
val = 'X: {:.2f}, Y: {:.2f}'.format(data_x + self._pixel_offset,
data_y + self._pixel_offset)
if image.wcs.wcs is not None:
ra, dec = image.pixtoradec(data_x, data_y)
val += ' (RA: {}, DEC: {})'.format(
Expand Down Expand Up @@ -196,7 +217,8 @@ def _mouse_click_cb(self, viewer, event, data_x, data_y):
self.center_on((data_x, data_y))

with self.print_out:
print('Centered on X={} Y={}'.format(data_x + 1, data_y + 1))
print('Centered on X={} Y={}'.format(data_x + self._pixel_offset,
data_y + self._pixel_offset))

# def _repr_html_(self):
# """
Expand Down Expand Up @@ -275,7 +297,7 @@ def load_array(self, arr):
"""
self._viewer.load_data(arr)

def center_on(self, point, pixel_coords_offset=1):
def center_on(self, point):
"""
Centers the view on a particular point.
Expand All @@ -284,17 +306,11 @@ def center_on(self, point, pixel_coords_offset=1):
point : tuple or `~astropy.coordinates.SkyCoord`
If tuple of ``(X, Y)`` is given, it is assumed
to be in data coordinates.
pixel_coords_offset : {0, 1}
Data coordinates provided are n-indexed,
where n is the given value.
This is ignored if ``SkyCoord`` is provided.
"""
if isinstance(point, SkyCoord):
self._viewer.set_pan(point.ra.deg, point.dec.deg, coord='wcs')
else:
self._viewer.set_pan(*(np.asarray(point) - pixel_coords_offset))
self._viewer.set_pan(*(np.asarray(point) - self._pixel_offset))

def offset_to(self, dx, dy, skycoord_offset=False):
"""
Expand Down Expand Up @@ -405,7 +421,6 @@ def marker(self, val):
'Marker type "{}" not supported'.format(marker_type))

def get_markers(self, x_colname='x', y_colname='y',
pixel_coords_offset=1,
skycoord_colname='coord'):
"""
Return the locations of existing markers.
Expand All @@ -415,11 +430,7 @@ def get_markers(self, x_colname='x', y_colname='y',
x_colname, y_colname : str
Column names for X and Y data coordinates.
Coordinates returned are 0- or 1-indexed, depending
on ``pixel_coords_offset``.
pixel_coords_offset : {0, 1}
Data coordinates returned are n-indexed,
where n is the given value.
on ``self.pixel_offset``.
skycoord_colname : str
Column name for ``SkyCoord``, which contains
Expand Down Expand Up @@ -480,8 +491,8 @@ def get_markers(self, x_colname='x', y_colname='y',
sky_col = SkyCoord(radec_col[:, 0], radec_col[:, 1], unit='deg')

# Convert X,Y from 0-indexed to 1-indexed
if pixel_coords_offset != 0:
xy_col += pixel_coords_offset
if self._pixel_offset != 0:
xy_col += self._pixel_offset

# Build table
if include_skycoord:
Expand All @@ -494,7 +505,6 @@ def get_markers(self, x_colname='x', y_colname='y',
return markers_table

def add_markers(self, table, x_colname='x', y_colname='y',
pixel_coords_offset=1,
skycoord_colname='coord', use_skycoord=False):
"""
Creates markers in the image at given points.
Expand All @@ -512,12 +522,7 @@ def add_markers(self, table, x_colname='x', y_colname='y',
x_colname, y_colname : str
Column names for X and Y.
Coordinates can be 0- or 1-indexed, as
given by ``pixel_coords_offset``.
pixel_coords_offset : {0, 1}
Data coordinates provided are n-indexed,
where n is the given value.
This is ignored if ``use_skycoord=True``.
given by ``self.pixel_offset``.
skycoord_colname : str
Column name with ``SkyCoord`` objects.
Expand Down Expand Up @@ -555,11 +560,11 @@ def add_markers(self, table, x_colname='x', y_colname='y',
coord_x = table[x_colname].data
coord_y = table[y_colname].data
# Convert data coordinates from 1-indexed to 0-indexed
if pixel_coords_offset != 0:
if self._pixel_offset != 0:
# Don't use the in-place operator -= here...that modifies
# the input table.
coord_x = coord_x - pixel_coords_offset
coord_y = coord_y - pixel_coords_offset
coord_x = coord_x - self._pixel_offset
coord_y = coord_y - self._pixel_offset

# Prepare canvas and retain existing marks
objs = []
Expand Down
45 changes: 41 additions & 4 deletions astrowidgets/tests/test_image_wdiget.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

import numpy as np
from astropy.table import Table
from astropy.wcs import WCS
Expand All @@ -19,15 +21,16 @@ def test_setting_image_width_height():
def test_add_marker_does_not_modify_input_table():
# Regression test for #45
# Adding markers should not modify the input data table
image = ImageWidget(image_width=300, image_height=300)
image = ImageWidget(image_width=300, image_height=300,
pixel_coords_offset=5)
data = np.random.random([300, 300])
image.load_array(data)
x = [20, 30, 40]
y = [40, 80, 100]
# Create two separate tables for comparison after add_markers.
orig_table = Table(data=[x, y], names=['x', 'y'])
in_table = Table(data=[x, y], names=['x', 'y'])
image.add_markers(in_table, pixel_coords_offset=5)
image.add_markers(in_table)
assert (in_table == orig_table).all()


Expand All @@ -45,7 +48,7 @@ def test_adding_markers_as_world_recovers_with_get_markers():
wcs.wcs.pc = [[0.000153051015113, -3.20700931602e-05],
[3.20704370872e-05, 0.000153072382405]]
fake_ccd = CCDData(data=fake_image, wcs=wcs, unit='adu')
iw = ImageWidget()
iw = ImageWidget(pixel_coords_offset=0)
iw.load_nddata(fake_ccd)
# Get me 100 positions please, not right at the edge
marker_locs = np.random.randint(10,
Expand All @@ -56,11 +59,45 @@ def test_adding_markers_as_world_recovers_with_get_markers():
marks_coords = SkyCoord(marks_world, unit='degree')
mark_coord_table = Table(data=[marks_coords], names=['coord'])
iw.add_markers(mark_coord_table, use_skycoord=True)
result = iw.get_markers(pixel_coords_offset=0)
result = iw.get_markers()
# Check the x, y positions as long as we are testing things...
np.testing.assert_allclose(result['x'], marks_pix['x'])
np.testing.assert_allclose(result['y'], marks_pix['y'])
np.testing.assert_allclose(result['coord'].ra.deg,
mark_coord_table['coord'].ra.deg)
np.testing.assert_allclose(result['coord'].dec.deg,
mark_coord_table['coord'].dec.deg)


def test_can_set_pixel_offset_at_object_level():
# The pixel offset below is nonsensical. It is chosen simply
# to make it easy to check for.
offset = 3
image = ImageWidget(image_width=300, image_height=300,
pixel_coords_offset=offset)
assert image._pixel_offset == offset


def test_move_callback_includes_offset():
# The pixel offset below is nonsensical. It is chosen simply
# to make it easy to check for.
offset = 3
image = ImageWidget(image_width=300, image_height=300,
pixel_coords_offset=offset)
data = np.random.random([300, 300])
image.load_array(data)
# Send a fake move to the callback. What gets put in the cursor
# value should be the event we sent in plus the offset.
image.click_center = True
data_x = 100
data_y = 200
image._mouse_move_cb(image._viewer, None, data_x, data_y)
output_contents = image._jup_coord.value
print(output_contents)
x_out = re.search(r'X: ([\d\.\d]+)', output_contents)
x_out = x_out.groups(1)[0]
y_out = re.search(r'Y: ([\d\.\d]+)', output_contents)
y_out = y_out.groups(1)[0]
assert float(x_out) == data_x + offset
assert float(y_out) == data_y + offset
# image.print_out.get_state()['outputs']

0 comments on commit 6ffc040

Please sign in to comment.