Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge image export to Master #108

Merged
merged 3 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ conda-recipe/ipyaladin

# Documentation
docs/_build/
docs/_collections/
docs/_collections/

# Examples
examples/*.png
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New way to make a selection on the view with `selection` method (#100)
- Add selected sources export as `astropy.Table` list with property `selected_objects` (#100)
- Add function `get_view_as_fits` to export the view as a `astropy.io.fits.HDUList` (#86)
- Add function `save_view_as_image` to save the view as an image file (#108)

### Deprecated

Expand Down
41 changes: 4 additions & 37 deletions examples/03_Functions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,10 @@
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"metadata": {},
"outputs": [],
"source": [
"help(aladin_bis.get_JPEG_thumbnail)"
"Save the view as an image"
]
},
{
Expand All @@ -154,42 +152,11 @@
"metadata": {},
"outputs": [],
"source": [
"aladin_bis.get_JPEG_thumbnail()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Check that your browser didn't block the popup window if you don't see the thumbnail. This will not work in VSCode or other notebooks editors that are not working in a browser."
"aladin_bis.save_view_as_image(\"4Sgr.png\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.8"
},
"vscode": {
"interpreter": {
"hash": "85bb43f988bdbdc027a50b6d744a62eda8a76617af1f4f9b115d38242716dbac"
}
}
},
"metadata": {},
"nbformat": 4,
"nbformat_minor": 4
}
26 changes: 23 additions & 3 deletions examples/11_Extracting_information_from_the_view.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"id": "998def1f-3963-405b-8be2-6d4ef4012634",
"metadata": {},
"source": [
"If you edit the view either by modifiing the widget through its interface, or programmatically: "
"If you edit the view either by modifiing the widget through its interface, or programmatically:"
]
},
{
Expand Down Expand Up @@ -106,7 +106,7 @@
"id": "f5add3a2-be30-488e-86df-426338b98f5d",
"metadata": {},
"source": [
"If you try to recover the value in the **same cell**, you'll get a `WidgetCommunicationError` error. This is because the calculation of the WCS is done by Aladin Lite *between* cell executions. \n",
"If you try to recover the value in the **same cell**, you'll get a `WidgetCommunicationError` error. This is because the calculation of the WCS is done by Aladin Lite *between* cell executions.\n",
"\n",
"## Getting the field of view\n",
"\n",
Expand Down Expand Up @@ -204,7 +204,7 @@
"metadata": {},
"source": [
"## Getting the view as a fits file\n",
"The following method allow you to retrieve the current view as a fits file. If a `path` is given as a second argument, the fits file will be saved."
"The following method allow you to retrieve the current view as a fits file."
]
},
{
Expand Down Expand Up @@ -247,6 +247,26 @@
"plt.subplot(projection=wcs)\n",
"plt.imshow(fits[0].data, cmap=\"binary_r\", norm=\"asinh\", vmin=0.001)"
]
},
{
"cell_type": "markdown",
"id": "c64190a2757b707",
"metadata": {},
"source": [
"## Saving the view as an image file\n",
"\n",
"In `save_view_as_image`, the first argument is the path to the file, the second is the format (\"png\", \"jpeg\", \"webp\"), and the third is a boolean to indicate if you want to include the Aladin Lite logo in the image."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85f40dc5e6af3ae1",
"metadata": {},
"outputs": [],
"source": [
"aladin.save_view_as_image(path=\"./crab.png\", image_format=\"png\", with_logo=True)"
]
}
],
"metadata": {
Expand Down
1 change: 1 addition & 0 deletions js/models/event_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export default class EventHandler {
this.eventHandlers = {
change_fov: this.messageHandler.handleChangeFoV,
goto_ra_dec: this.messageHandler.handleGotoRaDec,
save_view_as_image: this.messageHandler.handleSaveViewAsImage,
add_fits: this.messageHandler.handleAddFits,
add_catalog_from_URL: this.messageHandler.handleAddCatalogFromURL,
add_MOC_from_URL: this.messageHandler.handleAddMOCFromURL,
Expand Down
19 changes: 19 additions & 0 deletions js/models/message_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@ export default class MessageHandler {
this.aladin.gotoRaDec(msg["ra"], msg["dec"]);
}

async handleSaveViewAsImage(msg) {
const path = msg["path"];
const format = msg["format"];
const withLogo = msg["with_logo"];
const buffer = await this.aladin.getViewData(
"arraybuffer",
`image/${format}`,
withLogo,
);
this.model.send(
{
event_type: "save_view_as_image",
path: path,
},
null,
[buffer],
);
}

handleAddFits(msg, buffers) {
const options = convertOptionNamesToCamelCase(msg["options"] || {});
if (!options.name)
Expand Down
98 changes: 79 additions & 19 deletions src/ipyaladin/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class Aladin(anywidget.AnyWidget):
)
# reticle
show_reticle = Bool(
True, help="Wether to show the reticle in the middle of the view."
True, help="Whether to show the reticle in the middle of the view."
).tag(sync=True, init_option=True, only_init=True)
reticle_color = Unicode("rgb(178, 50, 178)", help="The color of the reticle.").tag(
sync=True, init_option=True, only_init=True
Expand All @@ -175,7 +175,7 @@ class Aladin(anywidget.AnyWidget):
False, help="Whether the coordinates grid should be shown at startup."
).tag(sync=True, init_option=True, only_init=True)
show_coo_grid_control = Bool(
True, help="Whether to show the coordinate grid control toolbar. "
True, help="Whether to show the coordinate grid control toolbar."
).tag(sync=True, init_option=True, only_init=True)
grid_color = Unicode(
"rgb(178, 50, 178)",
Expand Down Expand Up @@ -208,7 +208,7 @@ class Aladin(anywidget.AnyWidget):
overlay_survey_opacity = Float(0.0).tag(sync=True, init_option=True)
_base_layer_last_view = Unicode(
survey.default_value,
help="The last view of the base layer. It is used "
help="A private trait for the base layer of the last view. It is useful "
"to convert the view to an astropy.HDUList",
).tag(sync=True)

Expand All @@ -225,23 +225,24 @@ def __init__(self, *args: any, **kwargs: any) -> None:
self.fov = kwargs.get("fov", 60.0)
self.on_msg(self._handle_custom_message)

def _handle_custom_message(self, _: any, message: dict, __: any) -> None:
def _handle_custom_message(self, _: any, message: dict, buffers: any) -> None:
event_type = message["event_type"]
message_content = message["content"]
if (
event_type == "object_clicked"
and "object_clicked" in self.listener_callback
):
self.listener_callback["object_clicked"](message_content)
self.listener_callback["object_clicked"](message["content"])
elif (
event_type == "object_hovered"
and "object_hovered" in self.listener_callback
):
self.listener_callback["object_hovered"](message_content)
self.listener_callback["object_hovered"](message["content"])
elif event_type == "click" and "click" in self.listener_callback:
self.listener_callback["click"](message_content)
self.listener_callback["click"](message["content"])
elif event_type == "select" and "select" in self.listener_callback:
self.listener_callback["select"](message_content)
self.listener_callback["select"](message["content"])
elif event_type == "save_view_as_image":
self._save_file(message["path"], buffers[0])

@property
def selected_objects(self) -> List[Table]:
Expand Down Expand Up @@ -291,7 +292,8 @@ def wcs(self) -> WCS:
"""
if self._wcs == {}:
raise WidgetCommunicationError(
"The world coordinate system is not available. "
"The world coordinate system is not available. This often happens when "
"the WCS is modified and read in the same cell. "
"Please recover it from another cell."
)
if "RADECSYS" in self._wcs: # RADECSYS keyword is deprecated for astropy.WCS
Expand All @@ -310,7 +312,8 @@ def fov_xy(self) -> Tuple[Angle, Angle]:
"""
if self._fov_xy == {}:
raise WidgetCommunicationError(
"The field of view along the two axes is not available. "
"The field of view along the two axes is not available. This often "
"happens when the FOV is modified and read in the same cell. "
"Please recover it from another cell."
)
return (
Expand All @@ -330,6 +333,10 @@ def fov(self) -> Angle:
astropy.coordinates.Angle
An astropy.coordinates.Angle object representing the field of view.

See Also
--------
fov_xy

"""
return Angle(self._fov, unit="deg")

Expand Down Expand Up @@ -382,6 +389,50 @@ def target(self, target: Union[str, SkyCoord]) -> None:
}
)

def _save_file(self, path: str, buffer: bytes) -> None:
"""Save a file from a buffer.

Parameters
----------
path : str
The path where the file will be saved.
buffer : bytes
The buffer containing the file.

"""
with Path(path).open("wb") as file:
file.write(buffer)

def save_view_as_image(
self, path: Union[str, Path], image_format: str = "png", with_logo: bool = True
) -> None:
"""Save the current view of the widget as an image file.

Parameters
----------
path : Union[str, Path]
The path where the image will be saved.
image_format : str
The format of the image. Can be 'png', 'jpeg' or 'webp'.
with_logo : bool
Whether to include the Aladin Lite logo in the image.

See Also
--------
get_view_as_fits

"""
if image_format not in {"png", "jpeg", "webp"}:
raise ValueError("image_format must be 'png', 'jpeg' or 'webp")
self.send(
{
"event_name": "save_view_as_image",
"path": str(path),
"format": image_format,
"with_logo": with_logo,
}
)

def get_view_as_fits(self) -> HDUList:
"""Get the base layer of the widget as an astropy HDUList object.

Expand All @@ -394,6 +445,10 @@ def get_view_as_fits(self) -> HDUList:
astropy.io.fits.HDUList
The FITS object containing the image.

See Also
--------
save_view_as_image

"""
try:
from astroquery.hips2fits import hips2fits
Expand All @@ -415,6 +470,19 @@ def get_view_as_fits(self) -> HDUList:
) from e
return fits

def get_JPEG_thumbnail(self) -> None:
"""Create a new tab with the current Aladin view.

This method will only work if you are running a notebook in a browser (for
example, it won't do anything in VSCode).

See Also
--------
save_view_as_image: will save the image on disk instead

"""
self.send({"event_name": "get_JPG_thumbnail"})

def add_catalog_from_URL(
self, votable_URL: str, votable_options: Optional[dict] = None
) -> None:
Expand Down Expand Up @@ -726,14 +794,6 @@ def add_graphic_overlay_from_stcs(
}
)

def get_JPEG_thumbnail(self) -> None:
"""Create a popup window with the current Aladin view.

This method will only work if you are running a notebook in a browser (for
example, it won't do anything in VSCode).
"""
self.send({"event_name": "get_JPG_thumbnail"})

def set_color_map(self, color_map_name: str) -> None:
"""Change the color map of the Aladin Lite widget.

Expand Down
Loading