Compare commits

...

3 Commits

Author SHA1 Message Date
Matthieu Baumann
2a23e83c13 update fitsrs version 2025-10-20 14:41:30 +02:00
Matthieu Baumann
7b8272795d cargo clippy 2025-10-20 10:12:58 +02:00
Matthieu Baumann
4d8b4bfb21 Many fixes
* fix: HiPS3D probing spectra in galactic frame
* ui: new more option in the HiPS selector allowing to change the base layer with an arbitrary HiPS from the UI
* fits: support tile compressed image in bintable extensions (SRCNet feature: https://jira.skatelescope.org/browse/MAN-559)
2025-10-20 09:59:49 +02:00
30 changed files with 5558 additions and 5390 deletions

View File

@@ -12,7 +12,7 @@
import A from '../src/js/A.js';
//let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: "data/hips/CDS_P_DSS2_color", showReticle: true, showSurveyStackControl: true, showOverlayStackControl: false, projection: "TAN", target: '15 16 57.636 -60 55 7.49', showProjectionControl: true, realFullscreen: true, showZoomControl: true, showSimbadPointerControl: true, showShareControl: true, showContextMenu: true, showCooGridControl: true, fullScreen: true, showCooGrid: true, fov: 180, log: false});
aladin = A.aladin('#aladin-lite-div', {survey: "data/hips/CDS_P_DSS2_color", showReticle: true, showSurveyStackControl: true, showOverlayStackControl: false, projection: "TAN", target: '180 0', showProjectionControl: true, realFullscreen: true, showZoomControl: true, showSimbadPointerControl: true, showShareControl: true, showContextMenu: true, showCooGridControl: true, fullScreen: true, showCooGrid: true, fov: 180, log: false});
var moc_0_99 = A.MOCFromURL("./data/gw/gw_0.9.fits",{ name: "GW 90%", color: "#ff0000", opacity: 0.7, lineWidth: 10, fill: false, perimeter: true});
var moc_0_95 = A.MOCFromURL("./data/gw/gw_0.6.fits",{ name: "GW 60%", color: "#00ff00", opacity: 0.3, lineWidth: 3, fill: false, perimeter: true});

View File

@@ -28,7 +28,7 @@
}
);
hips = aladin.newImageSurvey("http://alasky.cds.unistra.fr/HIPS3D/LGLBSHI16", {
hips = aladin.newImageSurvey("https://alasky.cds.unistra.fr/HIPS3D/LGLBSHI-test-compression/", {
successCallback: (hips) => {
//hips.setFrequency({value: 6.374279333565797E-7, unit: "m"}) // GALFA
}

View File

@@ -7,31 +7,55 @@
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {fov: 30, survey: "CDS/P/GALEXGR6/AIS/FUV", target: "280 +0", projection: "AIT", showShareControl:true, showSettingsControl: true, showContextMenu:true});
let aladin = A.aladin('#aladin-lite-div', {fov: 30, target: "286.411023328 -37.3460065319", projection: "AIT", showShareControl:true, showSettingsControl: true, showContextMenu:true});
aladin.setOverlayImageLayer(A.image(
"https://nova.astrometry.net/image/25038473?filename=M61.jpg",
"data/img/m82.png",
{
name: "M61",
name: "M82",
wcs: {
NAXIS: 2, // Minimal header
CTYPE1: 'RA---TAN', // TAN (gnomic) projection
CTYPE2: 'DEC--TAN', // TAN (gnomic) projection
NAXIS: 2, // number of axes
NAXIS1: 3000, // image width
NAXIS2: 1918, // image height
CTYPE3: "RGB", // Tell Aladin this is RGB
WCSAXES: 2, // no comment
CTYPE1: "RA---TAN", // TAN (gnomic) projection + SIP distortions
CTYPE2: "DEC--TAN", // TAN (gnomic) projection + SIP distortions
EQUINOX: 2000.0, // Equatorial coordinates definition (yr)
LONPOLE: 180.0, // no comment
LATPOLE: 0.0, // no comment
CRVAL1: 185.445488837, // RA of reference point
CRVAL2: 4.47896032431, // DEC of reference point
CRPIX1: 588.995094299, // X reference pixel
CRPIX2: 308.307905197, // Y reference pixel
CUNIT1: 'deg', // X pixel scale units
CUNIT2: 'deg', // Y pixel scale units
CD1_1: -0.000223666022989, // Transformation matrix
CD1_2: -0.000296578064584, // no comment
CD2_1: -0.000296427555509, // no comment
CD2_2: 0.000223774308964, // no comment
NAXIS1: 1080, // Image width, in pixels.
NAXIS2: 705 // Image height, in pixels.
CRVAL1: 286.411023328, // RA of reference point
CRVAL2: -37.3460065319, // DEC of reference point
CRPIX1: 2264.1858724, // X reference pixel
CRPIX2: 583.14634196, // Y reference pixel
CUNIT1: "deg", // X pixel scale units
CUNIT2: "deg", // Y pixel scale units
CD1_1: -0.00284225200648, // Transformation matrix
CD1_2: 0.00145908284254, // no comment
CD2_1: -0.00145832184852, // no comment
CD2_2: -0.0028440175499, // no comment
A_ORDER: 2, // Polynomial order, axis 1
A_0_0: 0, A_0_1: 0, A_0_2: 1.97760279295e-7,
A_1_0: 0, A_1_1: 5.32298396638e-7,
A_2_0: -2.16045473726e-6,
B_ORDER: 2, // Polynomial order, axis 2
B_0_0: 0, B_0_1: 0, B_0_2: 3.97377848239e-7,
B_1_0: 0, B_1_1: -2.25823401545e-6,
B_2_0: -1.47800507759e-7,
AP_ORDER: 2, // Inv polynomial order, axis 1
AP_0_0: 0.00617616810622,
AP_0_1: -1.68315582233e-6,
AP_0_2: -1.96504899588e-7,
AP_1_0: -5.8320637913e-6,
AP_1_1: -5.26081207663e-7,
AP_2_0: 2.13760782681e-6,
BP_ORDER: 2, // Inv polynomial order, axis 2
BP_0_0: -0.000681183014773,
BP_0_1: -2.15389849968e-7,
BP_0_2: -3.94508397022e-7,
BP_1_0: 4.51837961352e-6,
BP_1_1: 2.24050293101e-6,
BP_2_0: 1.49195269783e-7
},
successCallback: (ra, dec, fov, image) => {
aladin.gotoRaDec(ra, dec);

View File

@@ -18,6 +18,10 @@
overlay.add(A.polyline([ [2.29452158, 59.14978110], [10.12683778, 56.53733116], [14.1772154, 60.7167403], [21.45396446, 60.23528403], [28.59885697, 63.67010079] ], {
opacity: 0.7
}));
aladin.on('rotationChanged', (rot) => {
aladin.setRotation(rot)
})
});
</script>
</body>

View File

@@ -24,8 +24,8 @@ enum_dispatch = "0.3.8"
wasm-bindgen = "=0.2.92"
async-channel = "1.8.0"
mapproj = "0.3.0"
fitsrs = "0.4.0"
colorgrad = "0.6.2"
fitsrs = "0.4.1"
[features]
webgl1 = [ "al-core/webgl1", "al-api/webgl1", "web-sys/WebGlRenderingContext", "web-sys/AngleInstancedArrays", "web-sys/ExtSRgb", "web-sys/OesTextureFloat",]

View File

@@ -179,8 +179,9 @@ impl HiPSProperties {
#[wasm_bindgen]
#[serde(rename_all = "camelCase")]
pub enum ImageExt {
#[serde(alias = "fits", alias = "fits.fz")]
Fits,
#[serde(alias = "fits.fz")]
FitsFz,
Jpeg,
Png,
Webp,
@@ -199,6 +200,7 @@ pub enum DataproductType {
impl std::fmt::Display for ImageExt {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ImageExt::FitsFz => write!(f, "fits.fz"),
ImageExt::Fits => write!(f, "fits"),
ImageExt::Png => write!(f, "png"),
ImageExt::Jpeg => write!(f, "jpg"),

View File

@@ -7,7 +7,7 @@ edition = "2018"
[dependencies]
js-sys = "0.3.47"
cgmath = "*"
fitsrs = "0.4.0"
fitsrs = "0.4.1"
al-api = { path = "../al-api" }
serde = { version = "^1.0.59", features = ["derive"] }
serde_json = "1.0"

View File

@@ -1,12 +1,13 @@
use crate::texture::format::TextureFormat;
use crate::texture::format::R8U;
use cgmath::Vector3;
use fitsrs::card::Value;
use fitsrs::hdu::data::bintable::data::BinaryTableData;
use fitsrs::hdu::data::bintable::tile_compressed::pixels::Pixels;
use fitsrs::hdu::header::extension::bintable::TileCompressedImage;
use fitsrs::hdu::header::Bitpix;
use fitsrs::hdu::header::Header;
use fitsrs::hdu::header::Xtension;
use fitsrs::WCS;
use fitsrs::{Fits, HDU};
use std::borrow::Cow;
use std::fmt::Debug;
use std::io::Cursor;
use std::ops::Range;
@@ -35,15 +36,7 @@ pub struct FitsImage<'a> {
// bytes offset where the data bytes are located inside the fits
pub data_byte_offset: Range<usize>,
// raw bytes of the data image (in Big-Endian)
pub raw_bytes: &'a [u8],
}
fn parse_keyword_as_number<X: Xtension + Debug>(header: &Header<X>, keyword: &str) -> Option<f32> {
match header.get(keyword) {
Some(Value::Integer { value, .. }) => Some(*value as f32),
Some(Value::Float { value, .. }) => Some(*value as f32),
_ => None,
}
pub raw_bytes: Cow<'a, [u8]>,
}
impl<'a> FitsImage<'a> {
@@ -65,13 +58,13 @@ impl<'a> FitsImage<'a> {
let header = hdu.get_header();
let bscale = parse_keyword_as_number(header, "BSCALE").unwrap_or(1.0);
let bzero = parse_keyword_as_number(header, "BZERO").unwrap_or(0.0);
let blank = parse_keyword_as_number(header, "BLANK");
let bscale = header.get_parsed::<f32>("BSCALE").unwrap_or(1.0);
let bzero = header.get_parsed::<f32>("BZERO").unwrap_or(0.0);
let blank = header.get_parsed::<f32>("BLANK").ok();
let trim1 = parse_keyword_as_number(header, "TRIM1").unwrap_or(0.0) as u32;
let trim2 = parse_keyword_as_number(header, "TRIM2").unwrap_or(0.0) as u32;
let trim3 = parse_keyword_as_number(header, "TRIM3").unwrap_or(0.0) as u32;
let trim1 = header.get_parsed::<u32>("TRIM1").unwrap_or(0);
let trim2 = header.get_parsed::<u32>("TRIM2").unwrap_or(0);
let trim3 = header.get_parsed::<u32>("TRIM3").unwrap_or(0);
let bitpix = hdu.get_header().get_xtension().get_bitpix();
@@ -79,7 +72,7 @@ impl<'a> FitsImage<'a> {
let len = hdu.get_data_unit_byte_size() as usize;
let data_byte_offset = off..(off + len);
let raw_bytes = &bytes[data_byte_offset.clone()];
let raw_bytes = Cow::Borrowed(&bytes[data_byte_offset.clone()]);
let wcs = hdu.wcs().ok();
@@ -100,6 +93,81 @@ impl<'a> FitsImage<'a> {
});
}
}
HDU::XBinaryTable(hdu) => {
let header = hdu.get_header();
let bin_table = header.get_xtension();
if let Some(TileCompressedImage {
z_bitpix: bitpix,
z_naxisn: naxis,
..
}) = &bin_table.get_z_image()
{
if naxis.len() >= 2 {
let width = naxis[0] as u32;
let height = naxis[1] as u32;
let depth = if naxis.len() >= 3 { naxis[2] as u32 } else { 1 };
let bscale = header.get_parsed::<f32>("BSCALE").unwrap_or(1.0);
let bzero = header.get_parsed::<f32>("BZERO").unwrap_or(0.0);
let blank = header.get_parsed::<f32>("BLANK").ok();
let trim1 = header.get_parsed::<u32>("TRIM1").unwrap_or(0);
let trim2 = header.get_parsed::<u32>("TRIM2").unwrap_or(0);
let trim3 = header.get_parsed::<u32>("TRIM3").unwrap_or(0);
let wcs = hdu.wcs().ok();
let off = hdu.get_data_unit_byte_offset() as usize;
let len = hdu.get_data_unit_byte_size() as usize;
let data_byte_offset = off..(off + len);
let mut bitpix = *bitpix;
let raw_bytes = match fits.get_data(&hdu) {
BinaryTableData::TileCompressed(Pixels::U8(pixels)) => {
Some(pixels.collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::I16(pixels)) => {
Some(pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::I32(pixels)) => {
Some(pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::F32(pixels)) => {
Some(pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>())
}
BinaryTableData::TileCompressed(Pixels::F64(pixels)) => {
bitpix = Bitpix::F32;
let raw_bytes =
pixels.flat_map(|p| p.to_be_bytes()).collect::<Vec<_>>();
Some(raw_bytes)
}
_ => None,
};
if let Some(raw_bytes) = raw_bytes {
images.push(Self {
trim1,
trim2,
trim3,
width,
height,
depth,
bitpix,
bscale,
wcs,
bzero,
blank,
data_byte_offset,
raw_bytes: Cow::Owned(raw_bytes),
});
}
}
}
}
_ => (),
}
}
@@ -140,6 +208,7 @@ impl Image for FitsImage<'_> {
R8U::view(&new_bytes)
}
Bitpix::F64 => {
// convert to i64 first
let new_bytes: Vec<_> = self
.raw_bytes
.chunks_exact(8)
@@ -153,7 +222,7 @@ impl Image for FitsImage<'_> {
R8U::view(&new_bytes)
}
_ => R8U::view(self.raw_bytes),
_ => R8U::view(&self.raw_bytes),
}
};

View File

@@ -267,6 +267,15 @@ impl App {
})
}
fn _update_hips_location(&mut self) {
let camera = &self.camera;
for hips in self.layers.get_mut_hipses() {
if let HiPS::D3(hips) = hips {
hips.set_cursor_location(camera);
}
}
}
fn look_for_new_tiles(&mut self) -> Result<(), JsValue> {
// Move the views of the different active hipss
self.tile_fetcher.clear();
@@ -508,7 +517,7 @@ impl App {
// The threshold stopping criteria must be dependant
// of the zoom level, in this case the initial angular distance
// speed
let thresh_speed = inertia.get_start_ampl() * 1e-3;
let thresh_speed = inertia.get_start_ampl() * 1e-4;
let cur_speed = inertia.get_cur_speed();
if cur_speed < thresh_speed {
@@ -516,8 +525,6 @@ impl App {
}
}
self.draw()?;
// Check for async retrieval
if let Ok(img) = self.img_recv.try_recv() {
let params = img.get_params();
@@ -548,7 +555,7 @@ impl App {
.get_resolved_tiles(/*&available_tiles, */&mut self.hipss);*/
if self.request_for_new_tiles
//&& Time::now() - self.last_time_request_for_new_tiles > DeltaTime::from(200.0)
&& Time::now() - self.last_time_request_for_new_tiles > DeltaTime::from(500.0)
{
self.look_for_new_tiles()?;
@@ -560,9 +567,9 @@ impl App {
let fetch_tiles =
// * the user is not panning the view
// * or the user is but did not move for at least 100ms
(Time::now() - self.camera.get_time_of_last_move() >= DeltaTime(100.0) || !self.dragging) &&
//(Time::now() - self.camera.get_time_of_last_move() >= DeltaTime(100.0) || !self.dragging) &&
// * no inertia action is in progress
self.inertia.is_none() &&
//self.inertia.is_none() &&
// * the user is not zooming
!self.camera.has_zoomed();
@@ -577,6 +584,18 @@ impl App {
const MAX_FRAME_TIME: DeltaTime = DeltaTime::from_millis(1000.0 / 40.0);
// - there is at least one tile in its blending phase
let blending_anim_occuring =
(Time::now() - self.time_start_blending) < BLENDING_ANIM_DURATION;
self.rendering = blending_anim_occuring
| has_camera_moved
| self.camera.has_zoomed()
| self.request_redraw
| self.inertia.is_some();
self.draw()?;
for rsc in rscs_received {
if Time::now() - rendering_timer >= MAX_FRAME_TIME {
self.downloader.borrow_mut().delay(rsc);
@@ -925,16 +944,6 @@ impl App {
}
}
// - there is at least one tile in its blending phase
let blending_anim_occuring =
(Time::now() - self.time_start_blending) < BLENDING_ANIM_DURATION;
self.rendering = blending_anim_occuring
| has_camera_moved
| self.camera.has_zoomed()
| self.request_redraw
| self.inertia.is_some();
// Reset the flags about the user action
self.camera.reset();
@@ -1031,64 +1040,63 @@ impl App {
self.layers.reset_frame();*/
//let scene_redraw = self.rendering | force_render;
let scene_redraw = true;
//let mut ui = self.ui.lock();
//let ui_redraw = ui.redraw_needed();
//if scene_redraw || ui_redraw {
if scene_redraw {
self.request_redraw = false;
let shaders = &mut self.shaders;
self.request_redraw = false;
let gl = self.gl.clone();
let shaders = &mut self.shaders;
let camera = &mut self.camera;
let gl = self.gl.clone();
let grid = &mut self.grid;
let moc = &mut self.moc;
let projection = &self.projection;
let camera = &mut self.camera;
let layers = &mut self.layers;
//let catalogs = &self.manager;
let colormaps = &self.colormaps;
//let fbo_view = &self._fbo_view;
//let final_rendering_pass = &self._final_rendering_pass;
let grid = &mut self.grid;
let moc = &mut self.moc;
let projection = &self.projection;
//fbo_view.draw_onto(
// move || {
// Render the scene
// Clear all the screen first (only the region set by the scissor)
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
let layers = &mut self.layers;
//let catalogs = &self.manager;
let colormaps = &self.colormaps;
//let fbo_view = &self._fbo_view;
//let final_rendering_pass = &self._final_rendering_pass;
// set the blending options
layers.draw(camera, shaders, colormaps, projection)?;
//fbo_view.draw_onto(
// move || {
// Render the scene
// Clear all the screen first (only the region set by the scissor)
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
// Draw the catalog
//let fbo_view = &self.fbo_view;
//catalogs.draw(&gl, shaders, camera, colormaps, fbo_view)?;
//catalogs.draw(&gl, shaders, camera, colormaps, None, self.projection)?;
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
moc.draw(camera, projection, shaders)?;
// set the blending options
layers.draw(camera, shaders, colormaps, projection)?;
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
grid.draw(camera, projection, shaders)?;
// Ok(())
// },
// None,
//)?;
// Draw the catalog
//let fbo_view = &self.fbo_view;
//catalogs.draw(&gl, shaders, camera, colormaps, fbo_view)?;
//catalogs.draw(&gl, shaders, camera, colormaps, None, self.projection)?;
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
moc.draw(camera, projection, shaders)?;
//final_rendering_pass.draw_on_screen(fbo_view, &mut self.shaders)?;
}
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);*/
grid.draw(camera, projection, shaders)?;
// Ok(())
// },
// None,
//)?;
//final_rendering_pass.draw_on_screen(fbo_view, &mut self.shaders)?;
Ok(())
}
@@ -1223,7 +1231,7 @@ impl App {
&gl,
wcs,
bitpix,
raw_bytes,
raw_bytes.as_ref(),
bscale,
bzero,
blank,
@@ -1532,6 +1540,8 @@ impl App {
// And stop the current inertia as well if there is one
self.inertia = None;
self._update_hips_location();
}
pub(crate) fn move_mouse(&mut self, s1x: f32, s1y: f32, s2x: f32, s2y: f32) {
@@ -1740,6 +1750,8 @@ impl App {
self.prev_cam_position = prev_cam_position;
self.request_for_new_tiles = true;
self._update_hips_location();
}
} else {
// approx move
@@ -1767,11 +1779,9 @@ impl App {
self.prev_cam_position = prev_cam_position;
self.request_for_new_tiles = true;
} else {
//self.out_of_fov = true;
self._update_hips_location();
}
} else {
//self.out_of_fov = true;
}
}
}
@@ -1800,6 +1810,8 @@ impl App {
pub(crate) fn set_zoom_factor(&mut self, zoom_factor: f64) {
self.camera.set_zoom_factor(zoom_factor, &self.projection);
self._update_hips_location();
self.request_for_new_tiles = true;
self.request_redraw = true;
}

View File

@@ -592,7 +592,7 @@ impl CameraViewPort {
(depth_pixel - DEPTH_OFFSET_TEXTURE) as u8
};
*/
let w_screen_device_px = self.width as f64;
let w_screen_device_px = self.width as f64 / (self.dpi as f64);
//let depth_pixel = 29_usize;
let pixel_angle_rad = self.get_aperture() / w_screen_device_px;

View File

@@ -147,7 +147,7 @@ impl From<query::Allsky> for AllskyRequest {
let FitsImage {
raw_bytes, bitpix, ..
} = FitsImage::from_raw_bytes(raw_bytes.as_slice())?[0];
} = &FitsImage::from_raw_bytes(raw_bytes.as_slice())?[0];
match bitpix {
Bitpix::U8 => {
Ok(handle_allsky_fits(raw_bytes, tile_size, allsky_tile_size)?

View File

@@ -3,7 +3,6 @@ use cgmath::Vector3;
use crate::camera::CameraViewPort;
use crate::math::angle::ToAngle;
use crate::math::projection::ProjectionType;
use crate::time::Time;
/// State for inertia
pub struct Inertia {
// Initial angular distance
@@ -12,14 +11,12 @@ pub struct Inertia {
// Vector of rotation
axis: Vector3<f64>,
// The time when the inertia begins
time_prev: Time,
north_up: bool,
}
impl Inertia {
pub fn new(ampl: f64, axis: Vector3<f64>, north_up: bool) -> Self {
Inertia {
time_prev: Time::now(),
ampl,
speed: (ampl * 0.5).min(0.1),
axis,
@@ -53,14 +50,13 @@ impl Inertia {
}*/
pub fn apply(&mut self, camera: &mut CameraViewPort, proj: &ProjectionType, dt: f64) {
self.time_prev = Time::now();
// Initial angular velocity
//let v0 = self.ampl * 0.5;
// Friction coefficient (tweak this)
let damping = 5e-3;
const DAMPING_FACTOR: f64 = 5e-3;
self.speed *= (-damping * dt).exp();
self.speed *= (-DAMPING_FACTOR * dt).exp();
let delta_angle = self.speed * dt;
// Exponential decay of angular velocity

View File

@@ -77,7 +77,7 @@ impl HiPSConfig {
}
let format = match img_ext {
ImageExt::Fits => {
ImageExt::Fits | ImageExt::FitsFz => {
// Check the bitpix to determine the internal format of the tiles
if let Some(bitpix) = bitpix {
let fmt = (match bitpix {
@@ -176,7 +176,7 @@ impl HiPSConfig {
pub fn set_image_ext(&mut self, ext: ImageExt) -> Result<(), JsValue> {
let format = match ext {
ImageExt::Fits => {
ImageExt::Fits | ImageExt::FitsFz => {
// Check the bitpix to determine the internal format of the tiles
if let Some(bitpix) = self.bitpix {
let fmt = (match bitpix {

View File

@@ -61,7 +61,7 @@ fn create_hpx_texture_storage(
(
WebGlRenderingCtx::TEXTURE_MIN_FILTER,
// apply mipmapping
WebGlRenderingCtx::LINEAR_MIPMAP_LINEAR,
WebGlRenderingCtx::NEAREST_MIPMAP_NEAREST,
),
(
WebGlRenderingCtx::TEXTURE_MAG_FILTER,
@@ -77,6 +77,11 @@ fn create_hpx_texture_storage(
WebGlRenderingCtx::TEXTURE_WRAP_T,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
// Prevents r-coordinate wrapping (repeating)
(
WebGlRenderingCtx::TEXTURE_WRAP_R,
WebGlRenderingCtx::CLAMP_TO_EDGE,
),
];
match channel {
PixelType::RGBA8U => {

View File

@@ -4,11 +4,16 @@ pub mod texture;
use crate::browser_support::BrowserFeaturesSupport;
use crate::healpix::moc::FreqSpaceMoc;
use crate::math::angle::ToAngle;
use crate::math::lonlat;
use crate::math::lonlat::LonLatT;
use crate::math::spectra::SpectralUnit;
use crate::math::spectra::FREQ_MAX;
use crate::math::spectra::FREQ_MIN;
use crate::coosys;
use crate::CooSystem;
use crate::tile_fetcher::TileFetcherQueue;
use al_api::hips::DataproductType;
use al_api::hips::ImageExt;
@@ -400,9 +405,6 @@ impl HiPS3D {
camera: &CameraViewPort,
browser_features_support: &BrowserFeaturesSupport,
) {
// update the cursor center before downloading new tiles
self.set_cursor_location(camera.get_center().into(), camera);
// do not add tiles if the view is already at depth 0
let cfg = self.get_config();
let depth_tile = camera
@@ -589,7 +591,7 @@ impl HiPS3D {
let mut start = window_pixel_hash.start.max(domain_pixel_hash.start);
let mut end = window_pixel_hash.end.min(domain_pixel_hash.end);
if start < end {
if start <= end {
start = start - pixel_hash_0 - indices.start;
end = end - pixel_hash_0 - indices.start;
@@ -657,7 +659,15 @@ impl HiPS3D {
crate::event::send_custom_event("spectra", JsValue::from(spectra_js_obj));
}
pub fn set_cursor_location(&mut self, lonlat: LonLatT<f64>, camera: &CameraViewPort) {
pub fn set_cursor_location(&mut self, camera: &CameraViewPort) {
let (lon, lat) = lonlat::xyz_to_radec(&coosys::apply_coo_system(
camera.get_coo_system(),
CooSystem::ICRS,
camera.get_center(),
));
let lonlat = LonLatT(lon, lat);
let cfg = self.get_config();
let s_order = camera
.get_tile_depth()
@@ -915,6 +925,9 @@ impl HiPS3D {
idx + off_indices,
idx + 3 + off_indices,
]);
idx += 4;
// GL LINES
/*self.idx_vertices.extend([
idx + off_indices,
@@ -929,8 +942,6 @@ impl HiPS3D {
idx + 3 + off_indices,
idx + off_indices,
]);*/
idx += 4;
}
off_indices += pos.len() as u16;
@@ -946,23 +957,21 @@ impl HiPS3D {
}
}
{
let mut vao = self.vao.bind_for_update();
vao.update_array(
"position",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.position),
)
.update_array(
"uv",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.uv),
)
.update_element_array(
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.idx_vertices),
);
}
let mut vao = self.vao.bind_for_update();
vao.update_array(
"position",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.position),
)
.update_array(
"uv",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.uv),
)
.update_element_array(
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.idx_vertices),
);
}
fn reset_available_tiles(&mut self) -> bool {

View File

@@ -11,6 +11,7 @@ use al_core::texture::Texture3D;
use al_core::webgl_ctx::WebGlRenderingCtx;
use cgmath::Vector3;
use fitsrs::hdu::header::Bitpix;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::Range;
use wasm_bindgen::JsValue;
@@ -283,37 +284,45 @@ impl HpxFreqTex {
) -> Result<(), JsValue> {
let raw_bytes = raw_bytes.to_vec().into_boxed_slice();
let (trim1, trim2, trim3, width, height, depth, bitpix, data_byte_offset, bscale, bzero) = {
let fits = FitsImage::from_raw_bytes(&raw_bytes[..])?;
fits[0].insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
self.data = {
let image = FitsImage::from_raw_bytes(&raw_bytes[..])?.pop().unwrap();
image.insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
(
fits[0].trim1,
fits[0].trim2,
fits[0].trim3,
fits[0].width,
fits[0].height,
fits[0].depth,
fits[0].bitpix,
fits[0].data_byte_offset.clone(),
fits[0].bscale,
fits[0].bzero,
)
let bitpix = image.bitpix;
let trim = (image.trim1, image.trim2, image.trim3);
let naxis = (image.width, image.height, image.depth);
let bscale = image.bscale;
let bzero = image.bzero;
if let Cow::Owned(uncompressed_bytes) = image.raw_bytes {
Some(HpxFreqData::Fits {
data_byte_offset: 0..uncompressed_bytes.len(),
raw_bytes: uncompressed_bytes.into_boxed_slice(),
bitpix,
trim,
naxis,
bscale,
bzero,
size,
})
} else {
let data_byte_offset = image.data_byte_offset.clone();
std::mem::drop(image);
Some(HpxFreqData::Fits {
raw_bytes,
data_byte_offset,
bitpix,
trim,
naxis,
bscale,
bzero,
size,
})
}
};
let trim = (trim1, trim2, trim3);
let naxis = (width, height, depth);
self.data = Some(HpxFreqData::Fits {
raw_bytes,
data_byte_offset: data_byte_offset.clone(),
bitpix,
trim,
naxis,
bscale,
bzero,
size,
});
self.num_stored_slices = self.num_slices;
self.start_time = Some(Time::now());

View File

@@ -689,7 +689,7 @@ impl Image {
.unproj_lonlat(&ImgXY::new(0.5, 0.5))
.ok_or(JsValue::from_str("(0, 0) does not lie in the sky"))?,
self.wcs
.unproj_lonlat(&ImgXY::new(width as f64 - 1.0, 0.5))
.unproj_lonlat(&ImgXY::new(width as f64 - 0.5, 0.5))
.ok_or(JsValue::from_str("(w - 1, 0) does not lie in the sky"))?,
self.wcs
.unproj_lonlat(&ImgXY::new(width as f64 - 0.5, height as f64 - 0.5))

View File

@@ -454,7 +454,11 @@ impl Layers {
// HiPS cube
DataproductType::Cube => HiPS::D3(HiPS3D::new(cfg, gl, &layer)?),
// HiPS 3D
DataproductType::SpectralCube => HiPS::D3(HiPS3D::new(cfg, gl, &layer)?),
DataproductType::SpectralCube => {
let mut hips = HiPS3D::new(cfg, gl, &layer)?;
hips.set_cursor_location(camera);
HiPS::D3(hips)
}
// Typical HiPS image
_ => HiPS::D2(HiPS2D::new(cfg, gl)?),
};

View File

@@ -28,4 +28,16 @@ where
pub fn is_ccw(&self) -> bool {
crate::math::utils::ccw_tri(self.v1, self.v2, self.v3)
}
pub fn is_elongated(&self) -> bool {
let mag2_12 = (self.v1[0] - self.v2[0]) * (self.v1[0] - self.v2[0])
+ (self.v1[1] - self.v2[1]) * (self.v1[1] - self.v2[1]);
let mag2_13 = (self.v1[0] - self.v3[0]) * (self.v1[0] - self.v3[0])
+ (self.v1[1] - self.v3[1]) * (self.v1[1] - self.v3[1]);
let mag2_23 = (self.v2[0] - self.v3[0]) * (self.v2[0] - self.v3[0])
+ (self.v2[1] - self.v3[1]) * (self.v2[1] - self.v3[1]);
let l = S::from(0.2).unwrap();
mag2_12 >= l || mag2_23 >= l || mag2_13 >= l
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -69,6 +69,7 @@ impl HiPSLocalFiles {
ImageExt::Jpeg => &self.tiles[1],
ImageExt::Png => &self.tiles[2],
ImageExt::Webp => &self.tiles[3],
ImageExt::FitsFz => todo!(),
};
tiles_per_fmt[d].get(&i)

View File

@@ -50,12 +50,6 @@ pub unsafe fn transmute_boxed_slice<I, O>(s: Box<[I]>) -> Box<[O]> {
Box::from_raw(out_slice_ptr)
}
#[allow(dead_code)]
pub unsafe fn transmute_vec_to_u8<I>(mut s: Vec<I>) -> Vec<u8> {
s.set_len(std::mem::size_of_val(&s[..]));
std::mem::transmute(s)
}
#[allow(dead_code)]
pub unsafe fn transmute_vec<I, O>(mut s: Vec<I>) -> Result<Vec<O>, &'static str> {
if !std::mem::size_of::<I>().is_multiple_of(std::mem::size_of::<O>()) {

View File

@@ -1831,7 +1831,7 @@ export let Aladin = (function () {
// Do not use proxy with CORS headers until we solve that: https://github.com/MattiasBuelens/wasm-streams/issues/20
//url = Utils.handleCORSNotSameOrigin(url).href;
let imageOptions = {...options, imgFormat: 'fits', successCallback, errorCallback};
let imageOptions = {...options, successCallback, errorCallback};
let image = new Image(url, imageOptions);
return image;
@@ -3117,7 +3117,6 @@ aladin.displayFITS(
get("https://alasky.unistra.fr/cgi/fits2HiPS", data).then(
async (response) => {
console.log(response, data)
if (response.status != "success") {
console.error("An error occured: " + response.message);
if (errorCallback) {

View File

@@ -918,7 +918,7 @@ export let HiPS = (function () {
imgFormat = "jpeg";
}
if (!["fits", "png", "jpeg", "webp"].includes(imgFormat)) {
if (!["fits", "png", "jpeg", "webp", "fits.fz"].includes(imgFormat)) {
console.warn('Formats must lie in ["fits", "png", "jpg", "webp"]. imgFormat option property ignored');
} else {
// Passed the check, we erase the image format with the new one
@@ -932,7 +932,7 @@ export let HiPS = (function () {
this.imgFormat = imgFormat;
let [minCut, maxCut] = this.getCuts();
if (minCut === undefined && maxCut === undefined && imgFormat === "fits") {
if (minCut === undefined && maxCut === undefined && (imgFormat === "fits" || imgFormat === "fits.fz")) {
// sets the default cuts parsed from the properties
this.setCuts(this.defaultFitsMinCut, this.defaultFitsMaxCut)
}

View File

@@ -261,7 +261,6 @@ export class SpectraDisplayer {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
let v = this.data.values[Math.round(mx / this.scaleX)]
let len = this.data.values.length;
@@ -325,7 +324,7 @@ export class SpectraDisplayer {
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const my = e.clientY - rect.top;
// can be in the spectral area
let v = this.data.values[Math.round(mx / this.scaleX)]
@@ -535,7 +534,7 @@ export class SpectraDisplayer {
this._redraw(this.ctx);
}
};
window.addEventListener("spectra", this.spectraUpdateCallback);
this.resetScale();

View File

@@ -797,6 +797,7 @@ export let View = (function () {
}
view.dragCoo = xymouse;
view.dragPastCoo = xymouse;
view.dragging = true;
@@ -817,39 +818,6 @@ export let View = (function () {
return true;
});
/*
Utils.on(view.catalogCanvas, "mouseup", function (e) {
e.preventDefault();
e.stopPropagation();
const xymouse = Utils.relMouseCoords(e);
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
state: {
mode: view.mode,
dragging: view.dragging,
rightClickPressed: view.rightClick
},
xy: xymouse,
ev: e,
});
if (view.rightClick) {
if (showContextMenu) {
view.aladin.contextMenu && view.aladin.contextMenu.show({e});
}
view.rightClick = false;
return;
}
if (view.mode === View.SELECT) {
view.selector.dispatch('mouseup', {coo: xymouse})
}
});
*/
Utils.on(view.catalogCanvas, "click", function (e) {
// call listener of 'click' event
@@ -876,6 +844,28 @@ export let View = (function () {
});
Utils.on(document, "mouseup", function(e) {
var wasDragging = view.realDragging === true;
if (view.dragging) { // if we were dragging, reset to default cursor
if(view.mode === View.PAN) {
view.setCursor('default');
}
view.dragging = false;
if (wasDragging) {
view.realDragging = false;
// call the positionChanged once more with a dragging = false
view.throttledPositionChanged(false);
}
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
} // end of "if (view.dragging) ... "
});
// reacting on 'click' rather on 'mouseup' is more reliable when panning the view
Utils.on(view.catalogCanvas, "mouseup mouseout touchend touchcancel", function (e) {
const xymouse = Utils.relMouseCoords(e);
@@ -916,26 +906,27 @@ export let View = (function () {
var wasDragging = view.realDragging === true;
if (view.dragging) { // if we were dragging, reset to default cursor
/*if (view.dragging) { // if we were dragging, reset to default cursor
if(view.mode === View.PAN) {
view.setCursor('default');
}
view.dragging = false;
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
if (wasDragging) {
view.realDragging = false;
// call the positionChanged once more with a dragging = false
view.throttledPositionChanged(false);
}
} // end of "if (view.dragging) ... "
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
} // end of "if (view.dragging) ... "*/
view.mustClearCatalog = true;
view.dragCoo = null;
view.dragPastCoo = null;
if (e.type === "mouseup") {
if (view.mode === View.SELECT) {
@@ -1003,7 +994,9 @@ export let View = (function () {
// TODO : remplacer par mecanisme de listeners
// on avertit les catalogues progressifs
view.refreshProgressiveCats();
view.wasm.releaseLeftButtonMouse();
if (wasDragging) {
view.wasm.releaseLeftButtonMouse();
}
});
var lastHoveredObject; // save last object hovered by mouse
@@ -1250,27 +1243,20 @@ export let View = (function () {
view.realDragging = true;
if (view.mode === View.PAN) {
view.pan = {
s1: view.dragCoo,
s2: xymouse
};
}
var s1 = view.dragCoo, s2 = xymouse;
// update drag coo with the new position
view.dragCoo = xymouse;
// update drag coo with the new position
/*if (view.mode == View.SELECT) {
view.requestRedraw();
return;
}*/
if (view.mode === View.PAN) {
view.wasm.moveMouse(s1.x, s1.y, s2.x, s2.y);
view.wasm.goFromTo(s1.x, s1.y, s2.x, s2.y);
view.updateCenter();
ALEvent.POSITION_CHANGED.dispatchedTo(view.aladin.aladinDiv, view.viewCenter);
// Apply position changed callback after the move
view.throttledPositionChanged(true);
}
}); //// endof mousemove ////
// disable text selection on IE
@@ -1391,9 +1377,6 @@ export let View = (function () {
* redraw the whole view
*/
View.prototype.redraw = function (now) {
// request another frame
requestAnimFrame(this.redrawClbk);
// Elapsed time since last loop
const elapsedTime = now - this.prevTime;
this.prevTime = now;
@@ -1410,8 +1393,27 @@ export let View = (function () {
this.zoomDelta -= step;
}
if (this.pan) {
let s1 = this.pan.s1;
let s2 = this.pan.s2;
if (s1 && s2) {
this.wasm.moveMouse(s1.x, s1.y, s2.x, s2.y);
this.wasm.goFromTo(s1.x, s1.y, s2.x, s2.y);
this.updateCenter();
ALEvent.POSITION_CHANGED.dispatchedTo(this.aladin.aladinDiv, this.viewCenter);
// Apply position changed callback after the move
this.throttledPositionChanged(true);
}
this.pan = null;
}
this.moving = this.wasm.update(elapsedTime);
// inertia run throttled position
if (this.moving && this.aladin.callbacksByEventName && this.aladin.callbacksByEventName['positionChanged'] && this.wasm.isInerting()) {
// run the trottled position
@@ -1424,6 +1426,9 @@ export let View = (function () {
this.drawAllOverlays();
}
this.needRedraw = false;
// request another frame
requestAnimFrame(this.redrawClbk);
};
View.prototype.drawAllOverlays = function () {
@@ -1701,6 +1706,10 @@ export let View = (function () {
}
View.prototype.setRotation = function(rotation) {
if (Math.abs(rotation - this.aladin.getRotation()) < 1e-5) {
return;
}
this.wasm.setRotation(rotation);
var rotationChangedCallback = this.aladin.callbacksByEventName["rotationChanged"];
typeof rotationChangedCallback === "function" && rotationChangedCallback(rotation);

View File

@@ -52,19 +52,29 @@ export class HiPSBrowserBox extends Box {
MocServer.getAllHiPSes().then((HiPSes) => {
HiPSBrowserBox.HiPSList = {}
self.HiPSTree = {};
// Fill the HiPSList from the MOCServer
// Build a hierarchy w.r.t sorted by regime
HiPSes.forEach((h) => {
let name = h.obs_title;
name = name.replace(/:|\'/g, '');
HiPSBrowserBox.HiPSList[name] = h;
self.HiPSTree[h.obs_regime] = self.HiPSTree[h.obs_regime] || {};
if (self.HiPSTree[h.obs_regime]) {
self.HiPSTree[h.obs_regime][name] = h
}
});
console.log("jkjk", self.HiPSTree)
// Initialize the autocompletion without any filtering
self._filterHiPSList({})
});
const _parseHiPS = (e) => {
const value = e.target.value;
@@ -272,6 +282,8 @@ export class HiPSBrowserBox extends Box {
return true;
};
filterEnabler.action({target: {checked: true}});
}
_addHiPS(id, name) {
@@ -430,7 +442,7 @@ export class HiPSBrowserBox extends Box {
_show(options) {
// Regenerate a new layer name
this.layer = Utils.uuidv4()
this.layer = (options && options.layer) || Utils.uuidv4();
if (this.filterBox)
this.filterBox.signalBrowserStatus(false)

View File

@@ -53,7 +53,7 @@ export class HiPSFilterBox extends Box {
url: freqIconUrl,
},
tooltip: {content: 'Observation bandwidth', position: {direction: 'bottom'}},
toggled: true,
toggled: false,
actionOn: () => {
self._triggerFilteringCallback();
},

View File

@@ -44,7 +44,6 @@ import settingsIconUrl from "../../../../assets/icons/settings.svg";
import searchIconImg from "../../../../assets/icons/search.svg";
import downloadIconUrl from '../../../../assets/icons/download.svg';
import { TogglerActionButton } from "../Button/Toggler.js";
import { Icon } from "../Widgets/Icon.js";
import { Box } from "../Widgets/Box.js";
@@ -731,6 +730,8 @@ export class OverlayStackBox extends Box {
// one must add the current HiPS too!
favoritesCopy.sort();
favoritesCopy.push("More...")
hips.HiPSSelector.update({value: currentHiPS, options: favoritesCopy});
}
});
@@ -745,14 +746,6 @@ export class OverlayStackBox extends Box {
}
}
/*if (this.hipsBrowser) {
this.hipsBrowser._hide();
}*/
/*if (this.catBox) {
this.catBox._hide();
}*/
if (this.addOverlayBtn) this.addOverlayBtn.hideMenu();
if (this.addHiPSBtn) this.addHiPSBtn.hideMenu();
@@ -781,22 +774,6 @@ export class OverlayStackBox extends Box {
})
);
layout = layout.concat(this._createSurveysList());
let self = this;
const moreHiPSLink = document.createElement("a");
moreHiPSLink.href = "#";
moreHiPSLink.classList.add('aladin-link');
moreHiPSLink.textContent = "More...";
moreHiPSLink.title = "Open the survey browser"
moreHiPSLink.addEventListener("click", (e) => {
e.preventDefault();
if (!self.hipsBrowser)
self.hipsBrowser = new HiPSBrowserBox(self.aladin);
self.hipsBrowser._show({ position: { anchor: "center center" } });
});
layout.push(moreHiPSLink)
return Layout.vertical({ layout });
}
@@ -945,6 +922,7 @@ export class OverlayStackBox extends Box {
options.push(value)
}
options.push("More...")
let HiPSSelector = Input.select({
value,
@@ -952,6 +930,14 @@ export class OverlayStackBox extends Box {
title: layer.name,
change: (e) => {
let name = e.target.value;
if (name === "More...") {
if (!self.hipsBrowser)
self.hipsBrowser = new HiPSBrowserBox(self.aladin);
self.hipsBrowser._show({ layer: layer.layer, position: { anchor: "center center" } });
return;
}
// search for the
let overlayLayer;
if (name in self.cachedHiPS) {

View File

@@ -47,6 +47,28 @@ export class DOMElement {
this.options = options;
this.name = options && options.name || Utils.uuidv4()
this.isHidden = true;
/*this.el.addEventListener("mouseup", (e) => {
var wasDragging = view.realDragging === true;
if (view.dragging) { // if we were dragging, reset to default cursor
if(view.mode === View.PAN) {
view.setCursor('default');
}
view.dragging = false;
if (wasDragging) {
view.realDragging = false;
// call the positionChanged once more with a dragging = false
view.throttledPositionChanged(false);
}
if (view.spectraDisplayer) {
view.spectraDisplayer.enableInteraction();
}
}
});*/
}
element() {