final commit

This commit is contained in:
Matthieu Baumann
2025-09-09 16:56:38 +02:00
committed by Matthieu Baumann
parent a10699c271
commit 634d652c54
12 changed files with 220 additions and 126 deletions

View File

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

View File

@@ -179,6 +179,7 @@ impl HiPSProperties {
#[wasm_bindgen]
#[serde(rename_all = "camelCase")]
pub enum ImageExt {
#[serde(alias = "fits", alias = "fits.fz")]
Fits,
Jpeg,
Png,

View File

@@ -675,11 +675,8 @@ impl App {
// TODO PNG/JPG case to handle here
match img {
ImageType::HTMLImageRgba8u {
image: HTMLImage { image, .. },
}
| ImageType::HTMLImageRgb8u {
image: HTMLImage { image, .. },
ImageType::ImageRgba8u {
image: Bitmap { image, .. },
} => {
let document = web_sys::window()
.unwrap_abort()
@@ -696,7 +693,7 @@ impl App {
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
// Get the data once for all for the whole image
// This takes time so better do it once and not repeatly
context.draw_image_with_html_image_element(
context.draw_image_with_image_bitmap(
image, 0.0, 0.0,
)?;
@@ -710,11 +707,6 @@ impl App {
.ceil()
as u32;
debug_assert_eq!(
num_rows * num_cols,
tile_depth
);
let tile_size = *tile_size;
let bytes = context
@@ -729,11 +721,12 @@ impl App {
let mut decoded_bytes = vec![
0_u8;
(tile_size * tile_size * tile_depth)
(tile_size * tile_size * tile_depth * 2)
as usize
];
let mut k = 0;
let mut num_tiles_cropped = 0;
for y in 0..num_rows {
let sy = y * tile_size;
@@ -749,23 +742,31 @@ impl App {
decoded_bytes[k] =
bytes[id_byte as usize];
k += 1;
decoded_bytes[k + 1] =
bytes[id_byte as usize + 3];
k += 2;
}
}
num_tiles_cropped += 1;
if num_tiles_cropped == tile_depth {
break;
}
}
if num_tiles_cropped == tile_depth {
break;
}
}
hips.push_tile_from_jpeg(
hips.push_tile_from_png(
cell,
decoded_bytes.into_boxed_slice(),
(tile_size, tile_size, tile_depth),
tile.request.time_request,
)?;
}
ImageType::ImageRgba8u {
image: Bitmap { image, .. },
}
| ImageType::ImageRgb8u {
ImageType::ImageRgb8u {
image: Bitmap { image, .. },
} => {
let document = web_sys::window()

View File

@@ -9,8 +9,11 @@ pub trait SpectralUnit: Into<Freq> + Clone + Copy {
use moclib::qty::{Frequency, MocQty};
pub const FREQ_MAX: Freq = Freq(5.846_006_549_323_611e48);
pub const FREQ_MIN: Freq = Freq(5.048_709_793_414_476e-29);
/// Frequency in Hz unit
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Freq(pub f64);
impl Freq {

View File

@@ -141,6 +141,24 @@ impl HiPS3DBuffer {
Ok(())
}
pub fn push_tile_from_png(
&mut self,
cell: &HEALPixFreqCell,
decoded_bytes: Box<[u8]>,
size: (u32, u32, u32),
time_request: Time,
) -> Result<(), JsValue> {
self.push_cell(cell, time_request)?;
let texture = self.textures.get_mut(cell).unwrap_abort();
// And copy the image in that cubic tile
texture.set_data_from_png(decoded_bytes, size)?;
self.available_tiles_during_frame = true;
Ok(())
}
// Tell if a texture is available meaning all its sub tiles
// must have been written for the GPU
pub fn contains_slice(

View File

@@ -6,6 +6,8 @@ use crate::healpix::moc::FreqSpaceMoc;
use crate::math::angle::ToAngle;
use crate::math::lonlat::LonLatT;
use crate::math::spectra::SpectralUnit;
use crate::math::spectra::FREQ_MAX;
use crate::math::spectra::FREQ_MIN;
use crate::tile_fetcher::TileFetcherQueue;
use al_api::hips::DataproductType;
@@ -145,7 +147,7 @@ impl Cursor {
let em_min = cfg.em_min.unwrap_abort();
let em_max = cfg.em_max.unwrap_abort();
let freq = em_min;
let freq = Freq((em_min.0 + em_max.0) * 0.5);
let location = LonLatT::new(0.0.to_angle(), 0.0.to_angle());
let f_max_order = cfg.max_depth_freq.unwrap_or(Frequency::<u64>::MAX_DEPTH);
@@ -276,12 +278,14 @@ impl Cursor {
}
fn set_freq(&mut self, freq: Freq) {
self.freq = freq;
if freq < FREQ_MAX && freq > FREQ_MIN {
self.freq = freq;
let s_order = self.cell.hpx.depth();
let f_order = self.f_max_order - (self.s_max_order - s_order);
let s_order = self.cell.hpx.depth();
let f_order = self.f_max_order - (self.s_max_order - s_order);
self.cell = HEALPixFreqCell::from_lonlat(self.location, self.freq, s_order, f_order);
self.cell = HEALPixFreqCell::from_lonlat(self.location, self.freq, s_order, f_order);
}
}
fn get_surrounding_cells_along_spectra_axis(
@@ -500,8 +504,6 @@ impl HiPS3D {
None
} else if let Some(moc) = self.moc.as_ref() {
if moc.intersects_cell(&cell) {
//al_core::log("not included in the moc");
Some(cell)
} else {
None
@@ -647,8 +649,6 @@ impl HiPS3D {
)
.unwrap_abort();
//al_core::log(&format!("{:?}", freqs));
Reflect::set(
&spectra_js_obj,
&JsValue::from_str("freqs"),
@@ -1157,6 +1157,24 @@ impl HiPS3D {
})
}
pub fn push_tile_from_png(
&mut self,
cell: &HEALPixFreqCell,
// the image slice
data: Box<[u8]>,
size: (u32, u32, u32),
time_request: Time,
) -> Result<(), JsValue> {
self.buffer
.push_tile_from_png(cell, data, size, time_request)
.map(|()| {
if self.cursor.is_contained_in_spectral_view(cell) {
// compute the spectra in case the cell is contained into the current spectral view
self.compute_spectra_on_cursor();
}
})
}
/* Accessors */
#[inline]
pub fn get_config(&self) -> &HiPSConfig {

View File

@@ -6,6 +6,7 @@ use crate::WebGlContext;
use al_core::image::fits::FitsImage;
use al_core::image::raw::ImageBuffer;
use al_core::image::Image;
use al_core::texture::format::RGBA8U;
use al_core::texture::format::{PixelType, R16I, R32F, R32I, R8U};
use al_core::texture::Texture3D;
use al_core::webgl_ctx::WebGlRenderingCtx;
@@ -38,6 +39,10 @@ pub enum HpxFreqData {
data: Box<[u8]>,
size: (u32, u32, u32),
},
Png {
data: Box<[u8]>,
size: (u32, u32, u32),
},
}
pub enum Pixel {
@@ -114,6 +119,12 @@ impl HpxFreqData {
let p = data[pixel_bytes_off];
Some(p as f32)
}
HpxFreqData::Png { data, size } => {
let pixel_bytes_off = (x + y * size.0 + z * (size.0 * size.1)) as usize;
let p = data[2 * pixel_bytes_off];
Some(p as f32)
}
}
}
}
@@ -204,15 +215,28 @@ impl HpxFreqTex {
let start_time = None;
let texture = match pixel_format {
PixelType::RGBA8U | PixelType::RGB8U | PixelType::R8U => {
Texture3D::create_empty::<R8U>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
)
}
// alpha transparency
PixelType::RGBA8U => Texture3D::create_empty::<R16I>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::RGB8U => Texture3D::create_empty::<R8U>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::R8U => Texture3D::create_empty::<R8U>(
gl,
tile_size as i32,
tile_size as i32,
num_slices as i32,
TEX_PARAMS,
),
PixelType::R32F => Texture3D::create_empty::<R32F>(
gl,
tile_size as i32,
@@ -325,6 +349,7 @@ impl HpxFreqTex {
size: (u32, u32, u32),
) -> Result<(), JsValue> {
let cubic_tile = ImageBuffer::<R8U>::new(decoded_bytes, size.0, size.1, size.2);
cubic_tile.insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
self.data = Some(HpxFreqData::Jpeg {
@@ -337,6 +362,27 @@ impl HpxFreqTex {
Ok(())
}
pub fn set_data_from_png(
&mut self,
// the tile image of the whole cubic tile
decoded_bytes: Box<[u8]>,
// size of the cube
size: (u32, u32, u32),
) -> Result<(), JsValue> {
let cubic_tile = ImageBuffer::<R16I>::new(decoded_bytes, size.0, size.1, size.2);
cubic_tile.insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
self.data = Some(HpxFreqData::Png {
data: cubic_tile.data,
size,
});
self.num_stored_slices = self.num_slices;
self.start_time = Some(Time::now());
Ok(())
}
// Panic if cell is not contained in the texture
// Do nothing if the texture is full
// Return true if the tile is newly added

View File

@@ -15,18 +15,19 @@ uniform float reversed;
/////////////////////////////////////////////
/// RED sampler
vec4 uvw2c_r(vec3 uv) {
float v = texture(tex, uv).r;
vec2 va = texture(tex, uv).ra;
v = transfer_func(H, v, min_value, max_value);
va.x = transfer_func(H, va.x, min_value, max_value);
// apply reversed
v = mix(v, 1.0 - v, reversed);
va.x = mix(va.x, 1.0 - va.x, reversed);
return apply_tonal(colormap_f(v));
vec4 c = colormap_f(va.x);
return apply_tonal(c);
}
/// RGBA sampler
vec4 uvw2c_rgba(vec3 uv) {
vec4 uvw2c_rgba(vec3 uv) {
vec4 c = texture(tex, uv).rgba;
c.r = transfer_func(H, c.r, min_value, max_value);
@@ -39,6 +40,19 @@ vec4 uvw2c_rgba(vec3 uv) {
return apply_tonal(c);
}
vec4 uvw2c_ra(vec3 uv) {
vec2 c = texture(tex, uv).rg;
c.r = transfer_func(H, c.r, min_value, max_value);
// apply reversed
c.r = mix(c.r, 1.0 - c.r, reversed);
vec3 color = colormap_f(c.r).rgb;
return apply_tonal(vec4(color, c.g));
}
vec4 uvw2cmap_rgba(vec3 uv) {
float v = texture(tex, uv).r;
// apply transfer f
@@ -53,7 +67,6 @@ vec4 uvw2cmap_rgba(vec3 uv) {
/////////////////////////////////////////////
/// FITS sampler
vec4 val2c_f32(float x) {
float alpha = x * scale + offset;
alpha = transfer_func(H, alpha, min_value, max_value);

View File

@@ -1,6 +1,8 @@
#version 300 es
precision lowp float;
precision lowp sampler3D;
precision lowp isampler3D;
precision lowp usampler3D;
uniform sampler3D tex;
@@ -13,7 +15,7 @@ uniform float opacity;
void main() {
vec3 uv = vec3(frag_uv.xyz);
vec4 color = uvw2c_r(uv);
vec4 color = uvw2c_ra(uv);
out_frag_color = color;
out_frag_color.a = opacity * out_frag_color.a;

View File

@@ -372,8 +372,6 @@ export let HiPS = (function () {
this.query = new Promise(async (resolve, reject) => {
if (isIncompleteOptions) {
console.log(self.url)
// ID typed url
if (self.startUrl && isID) {
// First download the properties from the start url
@@ -581,6 +579,8 @@ export let HiPS = (function () {
return "jpeg";
} else if (acceptedFormats.indexOf("fits") >= 0) {
return "fits";
} else if (acceptedFormats.indexOf("fits.fz") >= 0) {
return "fits";
} else {
throw (
"Unsupported format(s) found in the properties: " +

View File

@@ -235,7 +235,7 @@ export class SpectraDisplayer {
divNode.appendChild(canvasLabels)
divNode.appendChild(unitSelector.element())
divNode.appendChild(autoCenterBtn.element())
divNode.appendChild(extractionBtn.element())
//divNode.appendChild(extractionBtn.element())
this.divNode = divNode;
@@ -393,11 +393,13 @@ export class SpectraDisplayer {
df = self.data.freqs[self.data.freqs.length - 1] - self.data.freqs[self.data.freqs.length - 2];
f = self.data.freqs[self.data.freqs.length - 1] - dx * df;
}
self.hips.setFrequency({
value: f,
unit: 'Hz'
})
lastMouse = { x: mx, y: my };
}
});
@@ -538,8 +540,6 @@ export class SpectraDisplayer {
this.resetScale();
this.show()
//this.selector.update({value: hips.name, title: hips.name})
}
}

View File

@@ -36,7 +36,7 @@ import { Form } from "../Widgets/Form.js";
import pixelHistIconUrl from '../../../../assets/icons/pixel_histogram.svg';
import { RadioButton } from "../Widgets/Radio.js";
import waveOnIconUrl from '../../../../assets/icons/wave-on.svg';
import { TogglerActionButton } from "../Button/Toggler.js";
import { Layout } from "../Layout.js";
export class HiPSSettingsBox extends Box {
@@ -44,61 +44,58 @@ import { Form } from "../Widgets/Form.js";
constructor(aladin, options) {
let self;
let radioOptions = () => {
return {
luminosity: {
icon: {
size: 'small',
monochrome: true,
url: luminosityIconUrl
},
tooltip: {content: 'Contrast', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({
layout: [self.selector, self.luminositySettingsContent]
});
self.update({content})
}
let selector = new RadioButton({
luminosity: {
icon: {
size: 'small',
monochrome: true,
url: luminosityIconUrl
},
opacity: {
icon: {
size: 'small',
monochrome: true,
url: opacityIconUrl
},
tooltip: {content: 'Opacity', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [self.selector, self.opacitySettingsContent]});
self.update({content})
}
tooltip: {content: 'Contrast', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({
layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.luminositySettingsContent]
});
self.update({content})
}
},
opacity: {
icon: {
size: 'small',
monochrome: true,
url: opacityIconUrl
},
colors: {
icon: {
size: 'small',
url: colorIconUrl
},
tooltip: {content: 'Colormap', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [self.selector, self.colorSettingsContent]});
self.update({content})
}
tooltip: {content: 'Opacity', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.opacitySettingsContent]});
self.update({content})
}
},
colors: {
icon: {
size: 'small',
url: colorIconUrl
},
pixel: {
icon: {
size: 'small',
monochrome: true,
url: pixelHistIconUrl
},
tooltip: {content: 'Cutouts', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [self.selector, self.pixelSettingsContent]});
self.update({content})
}
tooltip: {content: 'Colormap', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.colorSettingsContent]});
self.update({content})
}
},
pixel: {
icon: {
size: 'small',
monochrome: true,
url: pixelHistIconUrl
},
selected: 'opacity'
}
};
let selector = new RadioButton(radioOptions(), aladin);
tooltip: {content: 'Cutouts', position: {direction: 'bottom'}},
action: (e) => {
const content = Layout.vertical({layout: [Layout.horizontal([self.selector, self.spectraBtn]), self.pixelSettingsContent]});
self.update({content})
}
},
selected: 'opacity'
}, aladin);
// Define the contents
@@ -288,8 +285,6 @@ import { Form } from "../Widgets/Form.js";
aladin.aladinDiv)
self = this;
this.radioOptions = radioOptions;
this.aladin = aladin;
this._addListeners()
@@ -337,30 +332,27 @@ import { Form } from "../Widgets/Form.js";
if (options.layer) {
let self = this;
if (options.layer.isSpectralCube()) {
self.selector = new RadioButton({
...this.radioOptions(),
spectra: {
icon: {
size: 'small',
monochrome: true,
url: waveOnIconUrl
},
tooltip: {content: 'Spectra', position: {direction: 'bottom'}},
action: (e) => {
let spectraDisplayer = self.aladin.view.spectraDisplayer;
if (spectraDisplayer.isHidden) {
spectraDisplayer.attachHiPS3D(options.layer)
spectraDisplayer.show()
} else {
spectraDisplayer.hide()
}
}
}
}, self.aladin);
let spectraDisplayer = self.aladin.view.spectraDisplayer;
console.log(self)
self.spectraBtn = new TogglerActionButton({
content: 'Spectra',
icon: {
size: 'small',
monochrome: true,
url: waveOnIconUrl
},
tooltip: {content: 'Show/hide spectra', position: {direction: 'bottom'}},
toggled: true,
actionOn: () => {
spectraDisplayer.attachHiPS3D(options.layer)
spectraDisplayer.show()
},
actionOff: () => {
spectraDisplayer.hide()
}
});
self.update({content: Layout.vertical([self.selector, self.opacitySettingsContent])})
self.update({content: Layout.vertical([Layout.horizontal([self.selector, self.spectraBtn]), self.opacitySettingsContent])})
}
this._update(options.layer)