Skip to content

Commit a7b182f

Browse files
committed
WIP more dummy viewer stuff
1 parent c9c16d9 commit a7b182f

File tree

1 file changed

+111
-23
lines changed

1 file changed

+111
-23
lines changed

src/astro_image_display_api/dummy_viewer.py

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ class ImageViewer:
2222
# These are attributes, not methods. The type annotations are there
2323
# to make sure Protocol knows they are attributes. Python does not
2424
# do any checking at all of these types.
25-
click_center: bool = False
26-
click_drag: bool = True
25+
_click_center: bool = False
26+
_click_drag: bool = True
2727
scroll_pan: bool = False
2828
image_width: int = 0
2929
image_height: int = 0
3030
zoom_level: float = 1
31-
is_marking: bool = False
31+
_is_marking: bool = False
3232
stretch_options: tuple = ("linear", "log", "sqrt")
33-
autocut_options: tuple = ("minmax", "zscale", "asinh", "percentile")
34-
cursor: str = ImageViewerInterface.ALLOWED_CURSOR_LOCATIONS[0]
33+
autocut_options: tuple = ("minmax", "zscale", "asinh", "percentile", "histogram")
34+
_cursor: str = ImageViewerInterface.ALLOWED_CURSOR_LOCATIONS[0]
3535
marker: Any = "marker"
36-
cuts: Any = (0, 1)
37-
stretch: str = "linear"
36+
_cuts: str | tuple[float] = (0, 1)
37+
_stretch: str = "linear"
3838
# viewer: Any
3939

4040
# Allowed locations for cursor display
@@ -59,6 +59,69 @@ class ImageViewer:
5959
_wcs: WCS | None = None
6060
_center: tuple[float, float] = (0.0, 0.0)
6161

62+
# Some properties where we need to control what happens
63+
@property
64+
def is_marking(self) -> bool:
65+
return self._is_marking
66+
67+
@property
68+
def click_center(self) -> bool:
69+
return self._click_center
70+
71+
@click_center.setter
72+
def click_center(self, value: bool) -> None:
73+
if self.is_marking:
74+
raise ValueError("Cannot set click_center while marking is active.")
75+
self._click_center = value
76+
self._click_drag = not value
77+
78+
@property
79+
def click_drag(self) -> bool:
80+
return self._click_drag
81+
@click_drag.setter
82+
def click_drag(self, value: bool) -> None:
83+
if self.is_marking:
84+
raise ValueError("Cannot set click_drag while marking is active.")
85+
self._click_drag = value
86+
self._click_center = not value
87+
88+
@property
89+
def stretch(self) -> str:
90+
return self._stretch
91+
92+
@stretch.setter
93+
def stretch(self, value: str) -> None:
94+
if value not in self.stretch_options:
95+
raise ValueError(f"Stretch option {value} is not valid. Must be one of {self.stretch_options}.")
96+
self._stretch = value
97+
98+
@property
99+
def cuts(self) -> tuple:
100+
return self._cuts
101+
102+
@cuts.setter
103+
def cuts(self, value: tuple) -> None:
104+
if isinstance(value, str):
105+
if value not in self.autocut_options:
106+
raise ValueError(f"Cut option {value} is not valid. Must be one of {self.autocut_options}.")
107+
# A real viewer would calculate the cuts based on the data
108+
self._cuts = (0, 1)
109+
return
110+
111+
if len(value) != 2:
112+
raise ValueError("Cuts must have length 2.")
113+
self._cuts = value
114+
115+
@property
116+
def cursor(self) -> str:
117+
return self._cursor
118+
119+
@cursor.setter
120+
def cursor(self, value: str) -> None:
121+
if value not in self.ALLOWED_CURSOR_LOCATIONS:
122+
raise ValueError(f"Cursor location {value} is not valid. Must be one of {self.ALLOWED_CURSOR_LOCATIONS}.")
123+
self._cursor = value
124+
62125
# The methods, grouped loosely by purpose
63126

64127
# Methods for loading data
@@ -106,7 +169,8 @@ def load_nddata(self, data: NDData) -> None:
106169
The NDData object to load.
107170
"""
108171
self._wcs = data.wcs
109-
self.image_height, self.image_width = data.shape
172+
# Not all NDDData objects have a shape, apparently
173+
self.image_height, self.image_width = data.data.shape
110174
# Totally made up number...as currently defined, zoom_level means, esentially, ratio
111175
# of image size to viewer size.
112176
self.zoom_level = 1.0
@@ -128,6 +192,9 @@ def save(self, filename: str | os.PathLike, overwrite: bool = False) -> None:
128192
`False`.
129193
"""
130194
p = Path(filename)
195+
if p.exists() and not overwrite:
196+
raise FileExistsError(f"File {filename} already exists. Use overwrite=True to overwrite it.")
197+
131198
p.write_text("This is a dummy file. The viewer does not save anything.")
132199

133200
# Marker-related methods
@@ -141,13 +208,13 @@ def start_marking(self, marker_name: str | None = None, marker: Any = None) -> N
141208
The name of the marker set to use. If not given, a unique
142209
name will be generated.
143210
"""
144-
self.is_marking = True
211+
self._is_marking = True
145212
self._previous_click_center = self.click_center
146213
self._previous_click_drag = self.click_drag
147214
self._previous_marker = self.marker
148215
self._previous_scroll_pan = self.scroll_pan
149-
self.click_center = False
150-
self.click_drag = False
216+
self._click_center = False
217+
self._click_drag = False
151218
self.scroll_pan = True
152219
self._interactive_marker_name = marker_name if marker_name else self.DEFAULT_INTERACTIVE_MARKER_NAME
153220
self.marker = marker if marker else self.DEFAULT_INTERACTIVE_MARKER_NAME
@@ -162,7 +229,7 @@ def stop_marking(self, clear_markers: bool = False) -> None:
162229
If `True`, clear the markers that were created during
163230
interactive marking. Default is `False`.
164231
"""
165-
self.is_marking = False
232+
self._is_marking = False
166233
self.click_center = self._previous_click_center
167234
self.click_drag = self._previous_click_drag
168235
self.scroll_pan = self._previous_scroll_pan
@@ -197,28 +264,42 @@ def add_markers(self, table: Table, x_colname: str = 'x', y_colname: str = 'y',
197264
The name of the marker set to use. If not given, a unique
198265
name will be generated.
199266
"""
267+
try:
268+
coords = table[skycoord_colname]
269+
except KeyError:
270+
coords = None
200271

201272
if use_skycoord:
202-
coords = table[skycoord_colname]
203273
if self._wcs is not None:
204274
x, y = self._wcs.world_to_pixel(coords)
205275
else:
206276
raise ValueError("WCS is not set. Cannot convert to pixel coordinates.")
207277
else:
208278
x = table[x_colname]
209279
y = table[y_colname]
280+
281+
if not coords and self._wcs is not None:
282+
coords = self._wcs.pixel_to_world(x, y)
283+
210284
if marker_name in self.RESERVED_MARKER_SET_NAMES:
211285
raise ValueError(f"Marker name {marker_name} not allowed.")
286+
212287
marker_name = marker_name if marker_name else self.DEFAULT_MARKER_NAME
288+
289+
to_add = Table(
290+
dict(
291+
x=x,
292+
y=y,
293+
coord=coords if coords else [None] * len(x),
294+
)
295+
)
296+
to_add["marker name"] = marker_name
297+
213298
if marker_name in self._markers:
214299
marker_table = self._markers[marker_name]
300+
self._markers[marker_name] = vstack([marker_table, to_add])
215301
else:
216-
marker_table = Table(names=["x", "y", "marker name"],
217-
dtype=[float, float, str])
218-
219-
to_add = table[x_colname, y_colname]
220-
to_add[marker_name] = marker_name
221-
self._markers[marker_name] = vstack([marker_table, to_add])
302+
self._markers[marker_name] = to_add
222303

223304
def reset_markers(self) -> None:
224305
"""
@@ -241,10 +322,14 @@ def remove_markers(self, marker_name: str | list[str] | None = None) -> None:
241322
del self._markers[marker_name]
242323
elif marker_name == "all":
243324
self._markers = {}
325+
else:
326+
raise ValueError(f"Marker name {marker_name} not found.")
244327
elif isinstance(marker_name, list):
245328
for name in marker_name:
246329
if name in self._markers:
247330
del self._markers[name]
331+
else:
332+
raise ValueError(f"Marker name {name} not found.")
248333

249334
def get_markers(self, x_colname: str = 'x', y_colname: str = 'y',
250335
skycoord_colname: str = 'coord',
@@ -278,12 +363,15 @@ def get_markers(self, x_colname: str = 'x', y_colname: str = 'y',
278363
marker_name = self._markers.keys()
279364
else:
280365
marker_name = [marker_name]
366+
elif marker_name is None:
367+
marker_name = [self.DEFAULT_MARKER_NAME]
281368

282369
to_stack = [self._markers[name] for name in marker_name if name in self._markers]
283370

284371
result = vstack(to_stack) if to_stack else Table(names=["x", "y", "coord", "marker name"])
372+
result.rename_columns(["x", "y", "coord"], [x_colname, y_colname, skycoord_colname])
285373

286-
return result.rename_columns(["x", "y", "coord"], [x_colname, y_colname, skycoord_colname])
374+
return result
287375

288376

289377
# Methods that modify the view
@@ -331,7 +419,7 @@ def offset_by(self, dx: float | Quantity, dy: float | Quantity) -> None:
331419
# This is a sky offset
332420
if self._wcs is not None:
333421
old_center_coord = self._wcs.pixel_to_world(self._center[0], self._center[1])
334-
new_center = old_center_coord.spherical_offsets_byt(dx, dy)
422+
new_center = old_center_coord.spherical_offsets_by(dx, dy)
335423
self.center_on(new_center)
336424
else:
337425
raise ValueError("WCS is not set. Cannot convert to pixel coordinates.")
@@ -340,7 +428,7 @@ def offset_by(self, dx: float | Quantity, dy: float | Quantity) -> None:
340428
new_center = (self._center[0] + dx.value, self._center[1] + dy.value)
341429
self.center_on(new_center)
342430

343-
def zoom(self) -> None:
431+
def zoom(self, val) -> None:
344432
"""
345433
Zoom in or out by the given factor.
346434
@@ -350,4 +438,4 @@ def zoom(self) -> None:
350438
The zoom level to zoom the image.
351439
See `zoom_level`.
352440
"""
353-
raise NotImplementedError
441+
self.zoom_level *= val

0 commit comments

Comments
 (0)