diff --git a/src/camera.rs b/src/camera.rs index 2aeedf7..230b2e1 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use arboard::Clipboard; use bevy::{ core_pipeline::clear_color::ClearColorConfig, @@ -84,7 +86,13 @@ pub enum CameraCommand { /// in-sync. Zoom(f32), - CopyToClipboard, + SaveTo(SaveToTarget), +} + +#[derive(Debug, Component, Clone)] +pub enum SaveToTarget { + Clipboard, + File(PathBuf), } /// Handle all camera events @@ -145,7 +153,7 @@ fn issue_camera_commands( transform.scale.y = *zoom; } } - CameraCommand::CopyToClipboard => { + CameraCommand::SaveTo(target) => { let size = images .get(&camera_setup.target.as_ref().unwrap()) .unwrap() @@ -180,11 +188,14 @@ fn issue_camera_commands( // TODO: update the size of cpu_target here to re - commands.spawn(ImageCopier::new( - camera_setup.target.as_ref().unwrap().clone(), - camera_setup.cpu_target.as_ref().unwrap().clone(), - size, - &render_device, + commands.spawn(( + ImageCopier::new( + camera_setup.target.as_ref().unwrap().clone(), + camera_setup.cpu_target.as_ref().unwrap().clone(), + size, + &render_device, + ), + target.clone(), )); // image_copier.disable();) @@ -204,11 +215,11 @@ fn issue_camera_commands( fn copy_to_clipboard( mut commands: Commands, - q_copier: Query<(Entity, &ImageCopier)>, + q_copier: Query<(Entity, &ImageCopier, &SaveToTarget)>, camera_setup: Res, mut images: ResMut>, ) { - for (entity, copier) in q_copier.iter() { + for (entity, copier, target) in q_copier.iter() { if copier.copy_count() == 0 && copier.render_count() == 0 { continue; } @@ -245,18 +256,23 @@ fn copy_to_clipboard( let data_length = data.len(); if expected_length == data.len() { - let img_data = arboard::ImageData { - width: size.x as usize, - height: size.y as usize, - bytes: data.into(), - }; - - if let Err(error) = ctx.set_image(img_data) { - commands.spawn(Message { - severity: Severity::Error, - message: format!("Error occured when trying to copy image to clipboard. This can happen if data is still loading in the background. Please try again.\n\nExpected size: {} x {}\nData size: {}\nData size(pre-filter): {}\n\nDescription: {:?}", - size.x, size.y, data_length, pre_data_length, error), - }); + match target { + SaveToTarget::Clipboard => { + let img_data = arboard::ImageData { + width: size.x as usize, + height: size.y as usize, + bytes: data.into(), + }; + + if let Err(error) = ctx.set_image(img_data) { + commands.spawn(Message { + severity: Severity::Error, + message: format!("Error occured when trying to copy image to clipboard. This can happen if data is still loading in the background. Please try again.\n\nExpected size: {} x {}\nData size: {}\nData size(pre-filter): {}\n\nDescription: {:?}", + size.x, size.y, data_length, pre_data_length, error), + }); + } + } + SaveToTarget::File(path) => {} } commands.entity(entity).despawn_recursive(); diff --git a/src/main.rs b/src/main.rs index cd822cd..0de14f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,17 +11,36 @@ //! - [x] Select channels to form RGB composite with user-specified thresholds //! - [x] View multiple IMC acquisitions in single .mcd file at once (multi-camera) //! - [ ] View multiple .mcd files +//! - [ ] Save/load current view/project //! - [x] Add annotatations and annotate data (e.g. with pencil tool) //! - [ ] Classify IMC data based on annotations //! - [ ] Segment cells based on classification data //! - [x] Load and visualise cell segmentation results //! - [ ] Calculate per-cell statistics (e.g. channel intensity) +//! - [ ] Cell phenotyping +//! - [ ] Network/neighbourhood analysis //! //! //! //! ## TODO: View multiple .mcd files //! - [ ] Implement means of dragging entire dataset //! - [ ] Provide option to load new dataset above loaded datasets +//! +//! ## TODO: Segment cells based on classification data +//! - [ ] Watershed on probability map (random forest) +//! - [ ] U-Net +//! +//! ## TODO: Calculate per-cell statistics (e.g. channel intensity) +//! - [ ] Link a cell to a specific acquisition +//! - [ ] Calculate cell area and shape(?) +//! - [ ] Calculate mean/std/median for each channel within cell +//! - [ ] Allow the selection of a single channel - colour each cell with mean/median of this channel data +//! - [ ] Export cell data to csv +//! +//! ## TODO: Cell phenotyping +//! - [ ] Manual thresholding / gating +//! - [ ] UMAP & density-based clustering +//! - [ ] Variational inference use std::path::PathBuf; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 9d42438..d7817aa 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -16,7 +16,7 @@ use crate::{ annotation::{AnnotationEvent, AnnotationPlugin}, camera::{ CameraCommand, CameraPlugin, CameraSetup, Draggable, FieldOfView, MousePosition, PanCamera, - Selectable, + SaveToTarget, Selectable, }, data::{CellSegmentation, DataCommand}, image_plugin::{ImageControl, ImageEvent, Opacity}, @@ -844,7 +844,16 @@ fn ui_camera_panel(world: &mut World, ui: &mut Ui) { let camera_setup = world.resource::(); if ui.button("Copy view to clipboard").clicked() { - camera_events.push(CameraCommand::CopyToClipboard); + camera_events.push(CameraCommand::SaveTo(SaveToTarget::Clipboard)); + } + if ui.button("Save view as file").clicked() { + if let Some(path) = rfd::FileDialog::new() + .add_filter("TIFF (.tif, .tiff)", &["tif", "tiff"]) + .add_filter("JPEG (.jpg, .jpeg)", &["jpg", "jpeg"]) + .save_file() + { + camera_events.push(CameraCommand::SaveTo(SaveToTarget::File(path))); + } } ui.horizontal(|ui| {