use std::sync::Arc;
use serde::{Deserialize, Serialize};
use pluot_core::{maybe_timeout, FutureExt, Duration};
use pluot_core::log;
use pluot_core::wgpu;
use pluot_core::cache::{use_memo_vec_f32, use_memo_vec_i32, use_memo_numeric_data};
use zarrs::storage::AsyncReadableStorageTraits;
use pluot_core::compute::reduce::reduce_extent;
use pluot_core::zarr::is_timed_out_zarrs_error;
use pluot_core::two::svg::{update_svg, SvgContext};
use pluot_core::render_traits::{CategoricalColormap, CategoricalParams, ColorMode, DrawToRasterGpu, DrawToRasterCpu, DrawToSvg, OpacityMode, PickableLayer, PreparedLayer, SizeMode, ViewParams, AspectRatioMode, UnitsMode, MarginParams, resolve_store_name};
use pluot_core::layers::point_layer::{PointLayer, PointShapeMode, PointLayerParams};
use pluot_core::numeric_data::NumericData;
use pluot_core::render_types::{CpuContext, CpuRenderPass, PrepareResult, RenderResult};
use pluot_core::render_types::GpuContext;
use pluot_core::LayerPickingResult;
use pluot_core::viewport::DataCoord;
use pluot_core::viewport::ScreenCoord;
use pluot_core::viewport::get_bounds;
use crate::zarr_numeric_data::load_arr_as_numeric_data;
use crate::zarr_emphasis_criteria::{resolve_zarr_emphasis_criteria, ZarrEmphasisCriteria};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(default)]
pub struct ZarrPointLayerParams {
pub layer_id: String,
pub bounds: Option<MarginParams>,
pub data_unit_mode_x: UnitsMode,
pub data_unit_mode_y: UnitsMode,
pub point_radius_unit_mode_x: UnitsMode,
pub point_radius_unit_mode_y: UnitsMode,
pub point_shape_mode: PointShapeMode,
pub model_matrix: Option<[f32; 16]>,
pub point_radius: Option<f32>, pub point_opacity: Option<f32>,
pub store_name: Option<String>,
pub x_key: String,
pub y_key: String,
pub color_key: Option<String>,
pub selection_criteria: Vec<ZarrEmphasisCriteria>,
pub filtering_criteria: Vec<ZarrEmphasisCriteria>,
pub background_fill_color: Option<(u8, u8, u8)>,
pub background_stroke_color: Option<(u8, u8, u8)>,
}
impl Default for ZarrPointLayerParams {
fn default() -> Self {
Self {
layer_id: "".to_string(),
bounds: None,
data_unit_mode_x: UnitsMode::Data,
data_unit_mode_y: UnitsMode::Data,
point_radius: Some(1.0),
point_radius_unit_mode_x: UnitsMode::Pixels,
point_radius_unit_mode_y: UnitsMode::Pixels,
point_shape_mode: PointShapeMode::Circle,
model_matrix: None,
point_opacity: Some(1.0),
store_name: None,
x_key: "".to_string(),
y_key: "".to_string(),
color_key: None,
selection_criteria: vec![],
filtering_criteria: vec![],
background_fill_color: None,
background_stroke_color: None,
}
}
}
pub struct ZarrPointLayerData {
pub x_arr: Arc<Vec<f32>>,
pub y_arr: Arc<Vec<f32>>,
pub labels_arr: Arc<Vec<i32>>,
}
pub struct ZarrPointLayer {
view_params: ViewParams,
layer_params: ZarrPointLayerParams,
store: Arc<dyn AsyncReadableStorageTraits>,
store_name: String,
inner: Option<PointLayer>,
}
impl ZarrPointLayer {
pub fn new(
view_params: ViewParams,
layer_params: ZarrPointLayerParams,
) -> Self {
if layer_params.point_radius_unit_mode_x == UnitsMode::Data && layer_params.data_unit_mode_x == UnitsMode::Pixels {
panic!("point_radius_unit_mode cannot be 'data' when data_unit_mode is 'pixels'");
}
if layer_params.point_radius_unit_mode_y == UnitsMode::Data && layer_params.data_unit_mode_y == UnitsMode::Pixels {
panic!("point_radius_unit_mode cannot be 'data' when data_unit_mode is 'pixels'");
}
let store_name = resolve_store_name(&layer_params.store_name, &view_params);
let store = view_params.get_store(&store_name);
Self {
view_params,
layer_params,
store,
store_name,
inner: None,
}
}
}
const BASE_POINT_SIZE: f32 = 5.0;
const LARGE_DATASET_COUNT: f32 = 10000.0;
const SMALL_DATASET_COUNT: f32 = 100.0;
fn get_initial_point_size(num_points: usize) -> f32 {
BASE_POINT_SIZE / (num_points as f32).clamp(SMALL_DATASET_COUNT, LARGE_DATASET_COUNT)
}
fn get_point_size_device_pixels(
device_pixel_ratio: f32,
x_range: f32,
y_range: f32,
visible_x: f32,
visible_y: f32,
width: f32,
height: f32,
num_points: usize,
) -> f32 {
let point_size = get_initial_point_size(num_points);
let point_screen_size_max = 10.0;
let point_screen_size_min = 2.0 / device_pixel_ratio;
let x_axis_range = 2.0 / (x_range / visible_x.max(f32::EPSILON));
let y_axis_range = 2.0 / (y_range / visible_y.max(f32::EPSILON));
let diagonal_screen_size = (width * width + height * height).sqrt();
let diagonal_axis_range = (x_axis_range * x_axis_range + y_axis_range * y_axis_range).sqrt();
let diagonal_fraction = point_size / diagonal_axis_range.max(f32::EPSILON);
let device_size = diagonal_fraction * diagonal_screen_size;
device_size.clamp(point_screen_size_min, point_screen_size_max)
}
fn get_point_opacity(
x_range: f32,
y_range: f32,
visible_x: f32,
visible_y: f32,
width: f32,
height: f32,
num_points: usize,
) -> f32 {
let n = num_points as f32;
let x = visible_y.max(f32::EPSILON);
let y = visible_x.max(f32::EPSILON);
let x0 = x_range;
let y0 = y_range;
let w = width;
let h = height;
let rho = (1.0 / 10.0_f32.powf(n.log10() - 3.0)).min(1.0);
let alpha = ((rho * w * h) / n) * (y0 / y) * (x0 / x);
alpha.clamp(2.01 / 255.0, 1.0)
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl PreparedLayer for ZarrPointLayer {
async fn prepare(&mut self, gpu_context: Option<&GpuContext<'_>>) -> PrepareResult {
let store = self.store.clone();
let l_i32_future_deps = vec!["l_bytes".to_string(), self.store_name.clone(), self.layer_params.layer_id.to_string()];
let l_i32_future = use_memo_vec_i32(async || {
let labels_array_path = &self.layer_params.color_key.as_ref().expect("Color key");
let labels_array_future = zarrs::array::Array::async_open(store.clone(), labels_array_path);
let labels_array = labels_array_future.await.unwrap();
let labels_subset = labels_array.subset_all();
let labels_vec = labels_array.async_retrieve_array_subset::<Vec<i64>>(&labels_subset).await?;
let labels_i32: Vec<i32> = labels_vec.iter().map(|&c| c as i32).collect();
Ok(labels_i32)
}, &l_i32_future_deps, self.view_params.cache_enabled);
let x_data_future_deps = vec!["x_bytes".to_string(), self.store_name.clone(), self.layer_params.layer_id.to_string()];
let x_data_future = use_memo_numeric_data(async || {
load_arr_as_numeric_data(store.clone(), &self.layer_params.x_key).await
}, &x_data_future_deps, self.view_params.cache_enabled);
let y_data_future_deps = vec!["y_bytes".to_string(), self.store_name.clone(), self.layer_params.layer_id.to_string()];
let y_data_future = use_memo_numeric_data(async || {
load_arr_as_numeric_data(store.clone(), &self.layer_params.y_key).await
}, &y_data_future_deps, self.view_params.cache_enabled);
let filtering_criteria_future_deps = vec!["filter_criteria".to_string(), self.store_name.clone(), self.layer_params.layer_id.to_string()];
let filtering_criteria_future = resolve_zarr_emphasis_criteria(
store.clone(),
&self.layer_params.filtering_criteria,
&filtering_criteria_future_deps,
self.view_params.cache_enabled,
);
let selection_criteria_future_deps = vec!["select_criteria".to_string(), self.store_name.clone(), self.layer_params.layer_id.to_string()];
let selection_criteria_future = resolve_zarr_emphasis_criteria(
store.clone(),
&self.layer_params.selection_criteria,
&selection_criteria_future_deps,
self.view_params.cache_enabled,
);
let futures_try_join_result = futures::try_join!(
maybe_timeout!(x_data_future, self.view_params.timeout),
maybe_timeout!(y_data_future, self.view_params.timeout),
maybe_timeout!(l_i32_future, self.view_params.timeout),
maybe_timeout!(filtering_criteria_future, self.view_params.timeout),
maybe_timeout!(selection_criteria_future, self.view_params.timeout),
);
let (x_data, y_data, l_i32, filtering_criteria, selection_criteria) = match futures_try_join_result {
Ok((x_data_result, y_data_result, l_i32_result, filtering_criteria_result, selection_criteria_result)) => {
match (x_data_result, y_data_result, l_i32_result, filtering_criteria_result, selection_criteria_result) {
(Ok(x), Ok(y), Ok(l), Ok(fc), Ok(sc)) => (x, y, l, fc, sc),
(Err(e), _, _, _, _) | (_, Err(e), _, _, _) | (_, _, Err(e), _, _) | (_, _, _, Err(e), _) | (_, _, _, _, Err(e)) => {
if is_timed_out_zarrs_error(&e) {
return PrepareResult { bailed_early: true };
} else {
panic!("Zarrs error during ZarrPointLayer prepare: {:?}", e);
}
}
}
}
Err(_) => {
return PrepareResult { bailed_early: true };
}
};
let (point_radius, point_opacity) = {
let auto_radius = self.layer_params.point_radius.is_none();
let auto_opacity = self.layer_params.point_opacity.is_none();
if !auto_radius && !auto_opacity {
(self.layer_params.point_radius.unwrap(), self.layer_params.point_opacity.unwrap())
} else {
let x_for_extent = x_data.as_ref().clone();
let y_for_extent = y_data.as_ref().clone();
let extent_future_deps = vec![
"point_extent".to_string(),
self.store_name.clone(),
self.layer_params.layer_id.clone(),
self.layer_params.x_key.clone(),
self.layer_params.y_key.clone(),
];
let extent = use_memo_vec_f32(async || {
let (x_min, x_max) = reduce_extent(gpu_context, x_for_extent, &[], &[]).await.background;
let (y_min, y_max) = reduce_extent(gpu_context, y_for_extent, &[], &[]).await.background;
Ok::<Vec<f32>, std::convert::Infallible>(vec![x_min, x_max, y_min, y_max])
}, &extent_future_deps, self.view_params.cache_enabled)
.await
.expect("Extent computation failed in ZarrPointLayer.prepare");
let (x_min, x_max, y_min, y_max) = (extent[0], extent[1], extent[2], extent[3]);
let num_points = x_data.len();
let x_range = (x_max - x_min).abs();
let y_range = (y_max - y_min).abs();
let visible = get_bounds(&self.view_params);
let visible_x = (visible.x_max - visible.x_min).abs();
let visible_y = (visible.y_max - visible.y_min).abs();
let (margin_top, margin_right, margin_bottom, margin_left) = match &self.view_params.margins {
Some(m) => (
m.margin_top.unwrap_or(0.0),
m.margin_right.unwrap_or(0.0),
m.margin_bottom.unwrap_or(0.0),
m.margin_left.unwrap_or(0.0),
),
None => (0.0, 0.0, 0.0, 0.0),
};
let layer_w = (self.view_params.width as f32 - (margin_left + margin_right)).max(1.0);
let layer_h = (self.view_params.height as f32 - (margin_top + margin_bottom)).max(1.0);
let point_radius = match self.layer_params.point_radius {
Some(radius) => radius,
None => get_point_size_device_pixels(
self.view_params.device_pixel_ratio,
x_range,
y_range,
visible_x,
visible_y,
layer_w,
layer_h,
num_points,
),
};
let point_opacity = match self.layer_params.point_opacity {
Some(opacity) => opacity,
None => get_point_opacity(
x_range,
y_range,
visible_x,
visible_y,
layer_w,
layer_h,
num_points,
),
};
(point_radius, point_opacity)
}
};
let mut sublayer = PointLayer::new(
self.view_params.clone(),
PointLayerParams {
layer_id: self.layer_params.layer_id.clone(),
bounds: self.layer_params.bounds.clone(),
data_unit_mode_x: self.layer_params.data_unit_mode_x,
data_unit_mode_y: self.layer_params.data_unit_mode_y,
point_radius: Some(SizeMode::UniformSize(point_radius)),
point_radius_unit_mode_x: self.layer_params.point_radius_unit_mode_x,
point_radius_unit_mode_y: self.layer_params.point_radius_unit_mode_y,
point_shape_mode: self.layer_params.point_shape_mode,
fill_opacity: Some(OpacityMode::UniformOpacity(point_opacity)),
model_matrix: self.layer_params.model_matrix,
fill_color: Some(ColorMode::Categorical(CategoricalParams {
codes: NumericData::Int32(l_i32.clone()),
colormap: CategoricalColormap::Category10,
})),
position_x: x_data.as_ref().clone(),
position_y: y_data.as_ref().clone(),
filtering_criteria,
selection_criteria,
background_fill_color: self.layer_params.background_fill_color,
background_stroke_color: self.layer_params.background_stroke_color,
..Default::default()
}
);
sublayer.prepare(gpu_context).await;
self.inner = Some(sublayer);
return PrepareResult {
bailed_early: false,
};
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DrawToRasterGpu for ZarrPointLayer {
async fn draw(&self, gpu_context: &GpuContext<'_>, pass: &mut wgpu::RenderPass) {
if let Some(inner) = &self.inner {
DrawToRasterGpu::draw(inner, gpu_context, pass).await;
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DrawToRasterCpu for ZarrPointLayer {
async fn draw(&self, _cpu_context: &CpuContext<'_>, _pass: &mut CpuRenderPass) {}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DrawToSvg for ZarrPointLayer {
async fn draw(&self, ctx: &mut SvgContext) {
if let Some(inner) = &self.inner {
DrawToSvg::draw(inner, ctx).await
}
}
}
impl PickableLayer for ZarrPointLayer {
fn pick(&self, screen_coord: ScreenCoord, data_coord: Option<DataCoord>) -> Option<LayerPickingResult> {
let DataCoord::TwoD { x: cx, y: cy } = data_coord? else {
return None;
};
if let Some(inner) = &self.inner {
return PickableLayer::pick(inner, screen_coord, data_coord);
}
return None;
}
}