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

feat: layers manipulations #84

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add function `save_view_as_image` to save the view as an image file (#108)
- Support planetary objects for ipyaladin targets (#103)
- new method `add_marker` to add a marker to the view (#111)
- Add function `add_hips` to add HiPS to the view (#84)
- Add function `remove_layer` to remove a layer from the view (#84)
- Add function `set_layer_opacity` to set the opacity of a layer (#84)

### Deprecated

Expand Down
35 changes: 31 additions & 4 deletions examples/02_Base_Commands.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@
"metadata": {},
"outputs": [],
"source": [
"aladin.overlay_survey = \"P/allWISE/color\"\n",
"aladin.overlay_survey_opacity = 0.5"
"aladin.add_hips(\"P/allWISE/color\", name=\"overlay\")\n",
"aladin.set_layer_opacity(\"overlay\", 0.5)"
]
},
{
Expand Down Expand Up @@ -137,6 +137,15 @@
"The target and field of view can be set with astropy objects"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from astropy.coordinates import Angle, SkyCoord"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -177,7 +186,8 @@
"metadata": {},
"source": [
"You can add markers to the view of the widget with custom popup title and description.\n",
"Here we will add markers for Messier objects M1 to M10."
"Here we will add markers for Messier objects M1 to M10.",
"You can get all layers identified by their name."
]
},
{
Expand All @@ -202,7 +212,24 @@
" )\n",
"aladin.add_markers(markers, name=\"M1-M10\", color=\"pink\", shape=\"cross\", source_size=15)\n",
"aladin.target = \"M1\"\n",
"aladin.fov = 0.2"
"aladin.fov = 0.2",
"aladin.layers"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And remove a layer by its name."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"aladin.remove_layer(\"M31\")"
]
}
],
Expand Down
86 changes: 69 additions & 17 deletions js/models/event_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,72 @@ export default class EventHandler {
this.aladin.setFoV(fov);
});

/* Survey control */
const jsSurveyLock = new Lock();
const pySurveyLock = new Lock();

this.model.on("change:survey", () => {
if (jsSurveyLock.locked) {
jsSurveyLock.unlock();
return;
}
pySurveyLock.lock();
this.aladin.setImageSurvey(this.model.get("survey"));
});

/* Overlay survey control */
const jsOverlaySurveyLock = new Lock();
const pyOverlaySurveyLock = new Lock();

this.model.on("change:overlay_survey", () => {
if (jsOverlaySurveyLock.locked) {
jsOverlaySurveyLock.unlock();
return;
}
pyOverlaySurveyLock.lock();
this.aladin.setOverlayImageLayer(this.model.get("overlay_survey"));
});

this.aladin.on("layerChanged", (imageLayer, layerName, state) => {
// If the layer is added or removed, update the layers traitlets
if (state === "ADDED") {
let layers = this.model.get("layers") || {};
// If the object is not copied, the model will not detect the change
layers = { ...layers };
if (imageLayer.url.startsWith("blob:"))
layers[layerName] = imageLayer.name;
else layers[layerName] = imageLayer.url;
this.model.set("layers", layers);

// If the layer is added, update the WCS, FoV, survey and overlay survey
if (layerName === "base") {
Xen0Xys marked this conversation as resolved.
Show resolved Hide resolved
this.updateWCS();
this.model.set("_survey_body", imageLayer.hipsBody || "sky");
this.model.set("_base_layer_last_view", imageLayer.url);
if (pySurveyLock.locked) {
pySurveyLock.unlock();
return;
}
jsSurveyLock.lock();
this.model.set("survey", imageLayer.url);
} else if (layerName === "overlay") {
if (pyOverlaySurveyLock.locked) {
pyOverlaySurveyLock.unlock();
return;
}
jsOverlaySurveyLock.lock();
this.model.set("overlay_survey", imageLayer.url);
}
} else if (state === "REMOVED") {
let layers = this.model.get("layers") || {};
// If the object is not copied, the model will not detect the change
layers = { ...layers };
delete layers[layerName];
this.model.set("layers", layers);
}
this.model.save_changes();
});

/* Div control */
this.model.on("change:_height", () => {
let height = this.model.get("_height");
Expand All @@ -151,15 +217,6 @@ export default class EventHandler {
this.model.save_changes();
});

this.aladin.on("layerChanged", (imageLayer, layerName, state) => {
if (layerName === "base")
this.model.set("_survey_body", imageLayer.hipsBody || "sky");
if (layerName !== "base" || state !== "ADDED") return;
this.updateWCS();
this.model.set("_base_layer_last_view", imageLayer.id);
this.model.save_changes();
});

this.aladin.on("resizeChanged", (width, height) => {
// Skip resize event when the div is hidden
if (width === 1 && height === 1) {
Expand Down Expand Up @@ -253,14 +310,6 @@ export default class EventHandler {
this.aladin.setFrame(this.model.get("coo_frame"));
});

this.model.on("change:survey", () => {
this.aladin.setImageSurvey(this.model.get("survey"));
});

this.model.on("change:overlay_survey", () => {
this.aladin.setOverlayImageLayer(this.model.get("overlay_survey"));
});

this.model.on("change:overlay_survey_opacity", () => {
this.aladin
.getOverlayImageLayer()
Expand All @@ -272,6 +321,9 @@ export default class EventHandler {
change_fov: this.messageHandler.handleChangeFoV,
goto_ra_dec: this.messageHandler.handleGotoRaDec,
save_view_as_image: this.messageHandler.handleSaveViewAsImage,
add_hips: this.messageHandler.handleAddHips,
remove_layer: this.messageHandler.handleRemoveLayer,
set_layer_opacity: this.messageHandler.handleSetLayerOpacity,
add_fits: this.messageHandler.handleAddFits,
add_catalog_from_URL: this.messageHandler.handleAddCatalogFromURL,
add_MOC_from_URL: this.messageHandler.handleAddMOCFromURL,
Expand Down
24 changes: 24 additions & 0 deletions js/models/message_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,30 @@ export default class MessageHandler {
);
}

handleAddHips(msg) {
const options = convertOptionNamesToCamelCase(msg["options"] || {});
if (!options.name)
options.name = `hips_${String(++imageCount).padStart(3, "0")}`;
const hipsIdOrUrl = msg["hips"];
const imageHips = A.imageHiPS(hipsIdOrUrl, options);
this.aladin.setOverlayImageLayer(imageHips, options.name);
}

handleRemoveLayer(msg) {
const layerName = msg["name"];
this.aladin.removeImageLayer(layerName);
}

handleSetLayerOpacity(msg) {
const layerName = msg["name"];
const opacity = msg["opacity"];
if (layerName === "overlay") {
this.model.set("overlay_survey_opacity", opacity);
this.model.save_changes();
}
this.aladin.getOverlayImageLayer(layerName).setAlpha(opacity);
}

handleAddFits(msg, buffers) {
const options = convertOptionNamesToCamelCase(msg["options"] || {});
if (!options.name)
Expand Down
68 changes: 66 additions & 2 deletions src/ipyaladin/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ class Aladin(anywidget.AnyWidget):
"sky",
help="The body name of the base layer survey, 'sky' for the sky survey",
).tag(sync=True, init_option=True)
# Surveys management
layers = traitlets.Dict(
{},
help="A dictionary of surveys to add to the widget. The keys are the names of "
"the surveys and the values are the URLs of the surveys.",
).tag(sync=True)
overlay_survey = Unicode("").tag(sync=True, init_option=True)
overlay_survey_opacity = Float(0.0).tag(sync=True, init_option=True)
_base_layer_last_view = Unicode(
Expand Down Expand Up @@ -538,6 +544,59 @@ def add_markers(
}
)

def add_hips(self, hips: str, **options: any) -> None:
"""Add a HiPS to the Aladin Lite widget.

Parameters
----------
hips : str
The HiPS to add to the widget as a URL or a CDS id
<https://aladin.cds.unistra.fr/hips/list>`_
options : keyword arguments
The options for the HiPS. See `Aladin Lite's HiPS options
<https://cds-astro.github.io/aladin-lite/global.html#HiPSOptions>`_

"""
self.send(
{
"event_name": "add_hips",
"hips": hips,
"options": options,
}
)

def remove_layer(self, name: str) -> None:
"""Remove a layer from the Aladin Lite widget.

Parameters
----------
name : str
The name of the layer to remove.

"""
if name == "base":
raise ValueError("The base layer cannot be removed.")
self.send({"event_name": "remove_layer", "name": name})

def set_layer_opacity(self, name: str, opacity: float) -> None:
"""Set the opacity of a layer in the Aladin Lite widget.

Parameters
----------
name : str
The name of the layer to set the opacity.
opacity : float
The opacity value to set.

"""
self.send(
{
"event_name": "set_layer_opacity",
"name": name,
"opacity": opacity,
}
)

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

Expand Down Expand Up @@ -686,8 +745,13 @@ def add_fits(self, fits: Union[str, Path, HDUList], **image_options: any) -> Non

self._wcs = {}
self.send(
{"event_name": "add_fits", "options": image_options},
buffers=[fits_bytes.getvalue()],
{
"event_name": "add_fits",
"options": image_options,
},
buffers=[
fits_bytes.getvalue(),
],
)

# MOCs
Expand Down
Loading