Created
June 10, 2018 04:35
-
-
Save eteq/bb625148969db51b2b03b71e92ca5231 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ImageWidget: | |
def __init__(self, properties...): # all the properties below should be valid kwargs | |
""" | |
Minimal widget does *not* have any fancy initializer parsing. The user | |
has to call a `load` method. Future-proofing against future `load_*` | |
methods so that the initializer doesn't get too confusing. | |
""" | |
# OPTION 2 / stretch goal - I favor only option 1: | |
def __init__(self, image=None, properties...): | |
""" | |
image can be a string to be interpreted as a fits file, an HDU, | |
an NDData, or a 2D array. These would just call the `load_*` methods. | |
""" | |
def _repr_html_(self): | |
""" | |
This yields the actual widget itself. That is, the user should be able | |
to simply do: | |
w = ImageWidget() | |
w | |
in a notebook and have it show the widget. | |
""" | |
def load_fits(self, fitsorfn): | |
""" | |
``fitsorfn`` can be either a string (a file name) or an HDU | |
(*not* an HDUList. Although that presents a subtle problem for fits | |
files where some of the WCS information is in other HDUs. Might have to | |
say that those require going through nddata) | |
""" | |
def load_nddata(self, nddata): | |
""" | |
``nddata`` must be an nddata object. | |
While the minimal version just uses the data and the wcs, future | |
enhancements could include flag/masking support, etc. | |
""" | |
def load_array(self, arr): | |
""" | |
``arr`` is a 2D array. No way to provide WCS, and this is | |
*intentional* - the user should create an `nddata` if they want wcs. | |
""" | |
def center_on(self, x, y): | |
""" | |
Centers the view on a particular point | |
""" | |
def offset_to(self, dx, dy): | |
""" | |
Moves the center to a point that's ``dx``/``dy`` away from the current | |
center. | |
""" | |
@property | |
def zoom_level(self): | |
""" | |
Settable, a float. If "1", means real-pixel-size. "2" means zoom out | |
by factor of 2, 0.5 means 2 screen pixels for 1 data pixel, etc. | |
Might be better as a getter/setter pair rather than property since it | |
may be performance-intensive? | |
""" | |
def zoom(self, val): | |
""" | |
Zoom in or out by the ``val`` factor. Presumably the "real" logic is | |
shared between this and `zoom_level`. | |
""" | |
def select_points(self): | |
""" | |
Enter "selection mode". This turns off ``click_drag``, and any click | |
will create a mark. | |
Later enhancements (second round): control the shape/size/color of the | |
selection marks a la the `add_marks` enhancement | |
""" | |
def get_selection(self): | |
""" | |
Return the locations of points from the most recent round of | |
selection mode. | |
Return value should be an astropy table, with "` and "y" columns | |
(or whatever the default column names are from ``add_marks``). If WCS | |
is present, should *also* have a "coords" column with a `SkyCoord` | |
object. | |
""" | |
def stop_selecting(self, clear_marks=True): | |
""" | |
Just what it says on the tin. | |
If ``clear_marks`` is False, the selected points are kept as visible | |
marks until ``reset_marks`` is called. Otherwise the marks disappear. | |
``get_selection()`` should still work even if ``clear_markers`` is | |
False, up until the next ``select_points`` call happens. | |
""" | |
def is_selecting(self): | |
""" | |
True if in selection mode, False otherwise. | |
""" | |
def add_marks(self, table, x_colname='x', y_colname='y', skycoord_colname='coord'): | |
""" | |
Creates markers in the image at given points. | |
Input is an astropy Table, and the column names for the x/y pixels will | |
be taken from the ``xcolname`` and ``ycolname`` kwargs. If the | |
``skycoord_colname`` is present, the table has the row, and WCS is | |
present on the image, mark the positions from the skycoord. If both | |
skycoord *and* x/y columns are present, raise an error about not knowing | |
which to pick. | |
Later enhancements (second round): more table columns to control | |
size/style/color of marks, ``remove_mark`` to remove some but not all | |
of the marks, let the initial argument be a skycoord or a 2xN array. | |
""" | |
def reset_marks(self): | |
""" | |
Delete all marks | |
""" | |
@property | |
def stretch(self): | |
""" | |
Settable. | |
One of the stretch objects from `astropy.visualization`, or something | |
that matches that API. | |
Note that this is *not* the same as the | |
Might be better as getter/setter rather than property since it may be | |
performance-intensive? | |
""" | |
def cuts(self): | |
""" | |
Settable. | |
One of the cut objects from `astropy.visualization`, or something | |
that matches that API | |
Might be better as getter/setter rather than property since it may be | |
performance-intensive? | |
""" | |
@property | |
def cursor(self): | |
""" | |
Settable. | |
If True, the pixel and possibly wcs is shown in the widget (see below), | |
if False, the position is not shown. | |
Possible enhancement: instead of True/False, could be "top", "bottom", | |
"left", "right", None/False | |
""" | |
@property | |
def click_drag(self): | |
""" | |
Settable. | |
If True, the "click-and-drag" mode is an available interaction for | |
panning. If False, it is not. | |
Note that this should be automatically made `False` when selection mode | |
is activated. | |
""" | |
@property | |
def click_center(self): | |
""" | |
Settable. | |
If True, middle-clicking can be used to center. If False, that | |
interaction is disabled. | |
In the future this might go from True/False to being a selectable | |
button. But not for the first round. | |
""" | |
@property | |
def scroll_pan(self): | |
""" | |
Settable. | |
If True, scrolling moves around in the image. If False, scrolling | |
(up/down) *zooms* the image in and out. | |
""" | |
def save(self, filename): | |
""" | |
Save out the current image view to an on-disk image. | |
""" | |
GUI_interactions = """ | |
* Right clicking should do ds9-style stretch adjustment. (*not* the same as | |
the ``stretch`` property - here I mean "brightness/contrast" adjustment | |
within the bounds of a given stretch) | |
* The user should be able to pan the view interactively. This can be via | |
middle clicking on the new center, click-and-drag, or scrolling (i.e. with | |
touchpad a la what ginga does). The properties ``click_drag`` ``click_center`` | |
and ``scroll`` can turn on/off these options (as does the "selection" mode). | |
* Zooming - if ``scroll_pan`` is False (probably the default), zooming is via | |
the scroll wheel. | |
* "Selection mode" - see `select_points` method. | |
* If the user provides an NDData or fits input (assuming the fits file has valid | |
WCS), if the cursor is not turned off it shows both the pixel coordinates and | |
the WCS coordinates under the cursor. | |
Initially, *no* keyboard shortcuts should be implemented. Eventually there | |
should be a clear mapping from keyboard shortcuts to methods, but until the | |
methods are stabilized, the keyboard shortcuts should be avoided. | |
""" | |
other_requirements = """ | |
* Should be able to hanle ~4k x 4k images without significant performance | |
lagging. | |
* Should be able to handle ~1000x markers without significant performance | |
degredation. | |
* Stretch goal: hould be able to handle ~10k x 10k images acceptable | |
* Extra-stretchy goal: handle very large datasets using a "tiling" approach. | |
This will presumably require different `load_*` functions, and more cleverness | |
on the JS side. | |
""" |
So as to keep discussion of this a bit more organized, I've moved it to https://github.com/eteq/nb-astroimage-api and made issues for the above comments.
Note there's also a trial implementation in https://github.com/astropy/astrowidgets , and we might move the discussion there later, but for now I'm thinking to keep the API itself and the trial implementation in separate repos
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wonder if we really want to insist that this is a "widget". It would be useful, I think, for it to work just as well for sending data/controls to an out-of-notebook display (e.g., DS9).
I think I would prefer "export" to "save" for the "Save out the current image view to an on-disk image." functionality, unless it's literally meant to be a file format that preserves all of the state of the viewer. I can't tell exactly what was meant by saving the current "view".
I am a bit concerned about the definition of the zoom level in terms of "physical pixels"; this seems less well defined in the current era of "Retina"-type displays where the O/S or browser's notion of a pixel may not be the same as the actual screen pixel. I would also suggest adding some way to say things like "zoom to fit" and "zoom to region" (x/y or skycoord) via the API, perhaps by keyword. "Zoom to region", in particular, has already been something that has been shown to be desired by users in the context of the comparable backend-independent LSST API. (The use case is putting up an image and the catalog of objects detected on that image, and then allowing a user to browse through that list of objects and zoom in to the region of the image on which that object was measured - its "footprint", in LSST-speak.)
Aside from those individual comments, it looks like an appropriate API. I'll look at it with the Firefly development team in some more detail soon.