├── .gitmodules ├── src ├── rust-toolchain ├── node_editor │ ├── .gitignore │ ├── src │ │ ├── id_stack.rs │ │ ├── constant_editor.rs │ │ ├── scrolling.rs │ │ ├── vec2.rs │ │ ├── export.rs │ │ └── node_state.rs │ ├── Cargo.toml │ └── examples │ │ └── minimal.rs ├── imgui_file_explorer │ ├── .gitignore │ ├── sample.png │ ├── README.md │ ├── Cargo.toml │ ├── examples │ │ ├── test_file_explorer.rs │ │ └── support │ │ │ └── mod.rs │ └── src │ │ └── lib.rs ├── aflak_primitives │ ├── test │ │ └── test.fits │ ├── Cargo.toml │ └── src │ │ ├── roi.rs │ │ ├── fits.rs │ │ └── precond.rs ├── variant_name_derive │ ├── example │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── variant_name │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── imgui_glium_support │ ├── Cargo.toml │ └── src │ │ ├── clipboard_support.rs │ │ └── lib.rs ├── imgui_tone_curve │ ├── Cargo.toml │ ├── examples │ │ ├── test_tone_curve.rs │ │ └── support │ │ │ └── mod.rs │ └── src │ │ └── lib.rs ├── aflak_cake │ ├── Cargo.toml │ ├── tests │ │ ├── algo.rs │ │ ├── serialize.rs │ │ ├── support │ │ │ └── mod.rs │ │ ├── macros.rs │ │ └── dst.rs │ ├── TODO │ └── src │ │ ├── future.rs │ │ ├── timed.rs │ │ ├── dst │ │ ├── node.rs │ │ └── iterators.rs │ │ ├── cache.rs │ │ └── lib.rs ├── aflak_plot │ ├── src │ │ ├── util.rs │ │ ├── err.rs │ │ ├── lib.rs │ │ ├── units.rs │ │ ├── imshow │ │ │ ├── hist.rs │ │ │ ├── mod.rs │ │ │ └── zscale.rs │ │ ├── scatter_lineplot │ │ │ └── mod.rs │ │ ├── plot │ │ │ └── mod.rs │ │ └── lims.rs │ ├── Cargo.toml │ └── examples │ │ ├── sin.rs │ │ ├── gradient.rs │ │ ├── wide_rectangle.rs │ │ ├── tall_rectangle.rs │ │ ├── peak.rs │ │ └── scatter.rs ├── src │ ├── cli.rs │ ├── build.rs │ ├── layout.rs │ ├── output_window │ │ ├── visualizable.rs │ │ └── mod.rs │ ├── file_dialog.rs │ ├── main.rs │ └── constant_editor.rs ├── release-build.sh └── Cargo.toml ├── images └── aflak-screen.png ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.56.0 2 | -------------------------------------------------------------------------------- /src/node_editor/.gitignore: -------------------------------------------------------------------------------- 1 | imgui.ini 2 | -------------------------------------------------------------------------------- /src/imgui_file_explorer/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /images/aflak-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aflak-vis/aflak/HEAD/images/aflak-screen.png -------------------------------------------------------------------------------- /src/aflak_primitives/test/test.fits: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aflak-vis/aflak/HEAD/src/aflak_primitives/test/test.fits -------------------------------------------------------------------------------- /src/imgui_file_explorer/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aflak-vis/aflak/HEAD/src/imgui_file_explorer/sample.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | **/target 3 | **/*.rs.bk 4 | Cargo.lock 5 | 6 | callgrind.out.* 7 | 8 | # Files generated/loaded by aflak 9 | *.ini 10 | *.ron 11 | *.fits 12 | 13 | # Text editors 14 | .vscode 15 | -------------------------------------------------------------------------------- /src/variant_name_derive/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | authors = ["Malik Olivier Boussejra "] 5 | 6 | [dependencies] 7 | variant_name_derive = { path = "../" } 8 | variant_name = { path = "../../variant_name" } 9 | -------------------------------------------------------------------------------- /src/imgui_file_explorer/README.md: -------------------------------------------------------------------------------- 1 | # imgui-file-explorer crate 2 | 3 | ![sample](sample.png) 4 | 5 | ## Run 6 | ```sh 7 | cargo run --example test 8 | ``` 9 | 10 | ## TODO 11 | - Consider and implement API 12 | - Get files' permissions 13 | - Change target directory/filtering by name or extension 14 | - Load some recent files 15 | -------------------------------------------------------------------------------- /src/variant_name_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "variant_name_derive" 3 | version = "0.0.1" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Derive VariantName trait" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | 9 | [dependencies] 10 | syn = "0.12" 11 | quote = "0.4" 12 | 13 | [lib] 14 | proc-macro = true 15 | -------------------------------------------------------------------------------- /src/variant_name/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "variant_name" 3 | version = "0.0.1" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Trait to extract the name of an enumeration's variants" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | 9 | [dependencies] 10 | 11 | [dev-dependencies] 12 | variant_name_derive = { path = "../variant_name_derive", version = "0.0.1" } 13 | -------------------------------------------------------------------------------- /src/node_editor/src/id_stack.rs: -------------------------------------------------------------------------------- 1 | use crate::cake::NodeId; 2 | 3 | /// Trait to get an i32 ID out of the implemented type. 4 | pub trait GetId { 5 | fn id(&self) -> i32; 6 | } 7 | 8 | impl GetId for NodeId { 9 | fn id(&self) -> i32 { 10 | match *self { 11 | NodeId::Transform(t_idx) => t_idx.id() as i32, 12 | NodeId::Output(output_id) => -(output_id.id() as i32), 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/imgui_glium_support/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aflak_imgui_glium_support" 3 | version = "0.0.3" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Support crate for using glium with imgui, made for aflak project" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | 9 | [dependencies] 10 | clipboard = "0.5" 11 | glium = { version = "0.30", default-features = true } 12 | imgui = { version = "0.8.0" } 13 | imgui-glium-renderer = { version = "0.8.0" } 14 | imgui-winit-support = { version = "0.8.0" } 15 | -------------------------------------------------------------------------------- /src/imgui_file_explorer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "imgui_file_explorer" 3 | version = "0.0.3" 4 | authors = ["Rikuo Uchiki ", "Malik Olivier Boussejra "] 5 | description = "File explorer plugin for imgui-rs" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | repository = "https://github.com/aflak-vis/aflak" 9 | 10 | [dependencies] 11 | cfg-if = "0.1" 12 | imgui = { version = "0.8.0" } 13 | 14 | [dev-dependencies] 15 | aflak_imgui_glium_support = { path = "../imgui_glium_support", version = "0.0.3" } 16 | -------------------------------------------------------------------------------- /src/imgui_tone_curve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "imgui_tone_curve" 3 | version = "0.0.3" 4 | authors = ["Rikuo Uchiki "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | imgui = { version = "0.8.0" } 11 | serde = {version = "1.0", features=["derive"] } 12 | serde_derive = "1.0" 13 | 14 | [dev-dependencies] 15 | glium = { version = "0.30", default-features = true } 16 | imgui-glium-renderer = { version = "0.8.0" } 17 | imgui-winit-support = { version = "0.8.0" } -------------------------------------------------------------------------------- /src/imgui_glium_support/src/clipboard_support.rs: -------------------------------------------------------------------------------- 1 | use clipboard::{ClipboardContext, ClipboardProvider}; 2 | use imgui::ClipboardBackend; 3 | 4 | pub struct ClipboardSupport(ClipboardContext); 5 | 6 | pub fn init() -> Option { 7 | ClipboardContext::new() 8 | .ok() 9 | .map(|ctx| ClipboardSupport(ctx)) 10 | } 11 | 12 | impl ClipboardBackend for ClipboardSupport { 13 | fn get(&mut self) -> Option { 14 | self.0.get_contents().ok() 15 | } 16 | fn set(&mut self, text: &str) { 17 | let _ = self.0.set_contents(text.to_owned()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/aflak_cake/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aflak_cake" 3 | version = "0.0.3" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Computational mAKE: Manage a graph of interdependent functions" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | 9 | [dependencies] 10 | variant_name = { path = "../variant_name", version = "0.0.1" } 11 | boow = "0.1" 12 | chashmap = "2.2" 13 | futures = "0.1" 14 | rayon = "1.0" 15 | serde = "1.0" 16 | serde_derive = "1.0" 17 | uuid = { version = "0.7.1", features = ["serde", "v4"] } 18 | 19 | [dev-dependencies] 20 | ron = "0.2" 21 | lazy_static = "1.0" 22 | variant_name_derive = { path = "../variant_name_derive", version = "0.0.1" } 23 | -------------------------------------------------------------------------------- /src/node_editor/src/constant_editor.rs: -------------------------------------------------------------------------------- 1 | use imgui::{self, DrawListMut, Ui}; 2 | 3 | /// Trait to define how to draw constant editor. 4 | /// 5 | /// Constant values may be edited in the node editor. 6 | /// Implementing this trait is necessary do define how the values flowing around 7 | /// in the node editor can be manually edited. 8 | pub trait ConstantEditor: Default { 9 | /// Build editor for constant T and return new value if value changed 10 | fn editor<'a, I>( 11 | &self, 12 | ui: &Ui, 13 | constant: &T, 14 | id: I, 15 | read_only: bool, 16 | draw_list: &DrawListMut, 17 | ) -> Option 18 | where 19 | I: Into>; 20 | } 21 | -------------------------------------------------------------------------------- /src/aflak_plot/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, Sub}; 2 | 3 | pub fn clamp(v: T, min: T, max: T) -> T 4 | where 5 | T: PartialOrd, 6 | { 7 | if v < min { 8 | min 9 | } else if v > max { 10 | max 11 | } else { 12 | v 13 | } 14 | } 15 | 16 | pub fn lerp(a: V, b: V, t: T) -> ::Output 17 | where 18 | V: Copy + Add + Sub + Mul, 19 | T: Into, 20 | { 21 | a + (b - a) * t.into() 22 | } 23 | 24 | pub fn to_u32_color(c: [u8; 3]) -> u32 { 25 | u32::from(c[0]) | (u32::from(c[1]) << 8) | (u32::from(c[2]) << 16) | 0xFF << 24 26 | } 27 | 28 | pub fn invert_color(c: u32) -> u32 { 29 | 0xFFFF_FFFF - c + 0xFF00_0000 30 | } 31 | -------------------------------------------------------------------------------- /src/aflak_plot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aflak_plot" 3 | version = "0.0.3" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Plotting library using imgui made for aflak" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | 9 | [dependencies] 10 | glium = { version = "0.30", default-features = true } 11 | imgui = { version = "0.8.0" } 12 | imgui-glium-renderer = { version = "0.8.0" } 13 | aflak_cake = { path = "../aflak_cake" } 14 | aflak_primitives = { path = "../aflak_primitives" } 15 | node_editor = { path = "../node_editor" } 16 | ndarray = "0.12" 17 | implot = { git = "https://github.com/4bb4/implot-rs" } 18 | meval = "0.2" 19 | 20 | [dev-dependencies] 21 | aflak_imgui_glium_support = { path = "../imgui_glium_support", version = "0.0.3" } 22 | -------------------------------------------------------------------------------- /src/variant_name_derive/example/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate variant_name_derive; 3 | extern crate variant_name; 4 | 5 | use variant_name::VariantName; 6 | 7 | #[derive(VariantName)] 8 | enum EnumTest { 9 | A, 10 | B(usize), 11 | C(usize, usize), 12 | D{ _a: usize, _b: usize }, 13 | } 14 | 15 | #[derive(VariantName)] 16 | struct StructTest; 17 | 18 | fn main() { 19 | println!("{}", EnumTest::A.variant_name()); 20 | println!("{}", EnumTest::B(2).variant_name()); 21 | println!("{}", EnumTest::C(3,1).variant_name()); 22 | println!("{}", EnumTest::D{_a: 1, _b: 3}.variant_name()); 23 | println!("{}", StructTest.variant_name()); 24 | 25 | println!("{:?}", EnumTest::variant_names()); 26 | println!("{:?}", StructTest::variant_names()); 27 | } 28 | -------------------------------------------------------------------------------- /src/node_editor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "node_editor" 3 | version = "0.0.3" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Node editor UI using imgui-rs as backend, made for aflak project" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | 9 | [dependencies] 10 | imgui = { version = "0.8.0" } 11 | imgui_file_explorer = { path = "../imgui_file_explorer", version = "0.0.3" } 12 | aflak_cake = { path = "../aflak_cake", version = "0.0.3" } 13 | ron = "0.6" 14 | serde = "1.0" 15 | serde_derive = "1.0" 16 | 17 | [dev-dependencies] 18 | aflak_imgui_glium_support = { path = "../imgui_glium_support", version = "0.0.3" } 19 | lazy_static = "1.0" 20 | variant_name = { path = "../variant_name", version = "0.0.1" } 21 | variant_name_derive = { path = "../variant_name_derive", version = "0.0.1" } 22 | -------------------------------------------------------------------------------- /src/aflak_primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aflak_primitives" 3 | version = "0.0.3" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Define primitive functions for use with aflak" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | 9 | [dependencies] 10 | aflak_cake = { path = "../aflak_cake", version = "0.0.3" } 11 | imgui_tone_curve = { path = "../imgui_tone_curve", version = "0.0.3" } 12 | fitrs = "0.5.0" 13 | lazy_static = "1.0" 14 | ndarray = { version = "0.12", features = ["serde-1"] } 15 | ndarray-parallel = "0.9.1" 16 | nalgebra = "0.30.1" 17 | serde = "1.0" 18 | serde_derive = "1.0" 19 | regex = "1.5" 20 | variant_name = { path = "../variant_name", version = "0.0.1" } 21 | variant_name_derive = { path = "../variant_name_derive", version = "0.0.1" } 22 | rawloader = "0.36.3" 23 | libm = "0.2.1" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: rust 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | os: 8 | - linux 9 | - osx 10 | - windows 11 | cache: cargo 12 | 13 | matrix: 14 | allow_failures: 15 | - os: windows 16 | 17 | before_install: 18 | - cd src 19 | 20 | script: 21 | - cargo build --all --verbose 22 | - cargo test --all --verbose 23 | 24 | jobs: 25 | include: 26 | - stage: fmt 27 | install: 28 | - rustup component add rustfmt-preview 29 | - cargo fmt --version 30 | script: 31 | - cargo fmt --all -- --check 32 | - stage: size-check 33 | rust: nightly 34 | install: 35 | - cargo install print-type-sizes 36 | - print-type-sizes --version 37 | script: 38 | - print-type-sizes --max-size 100000 39 | -------------------------------------------------------------------------------- /src/aflak_plot/src/err.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt}; 2 | 3 | use glium; 4 | 5 | /// A rendering error. 6 | #[derive(Debug)] 7 | pub enum Error { 8 | Msg(&'static str), 9 | Glium(glium::texture::TextureCreationError), 10 | } 11 | 12 | impl From<&'static str> for Error { 13 | fn from(s: &'static str) -> Error { 14 | Error::Msg(s) 15 | } 16 | } 17 | 18 | impl From for Error { 19 | fn from(e: glium::texture::TextureCreationError) -> Error { 20 | Error::Glium(e) 21 | } 22 | } 23 | 24 | impl fmt::Display for Error { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | match *self { 27 | Error::Msg(s) => s.fmt(f), 28 | Error::Glium(e) => write!(f, "Glium back-end error: {}", e), 29 | } 30 | } 31 | } 32 | 33 | impl error::Error for Error {} 34 | -------------------------------------------------------------------------------- /src/variant_name/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Trait to uniquely identify the variant of an enumeration. 2 | /// 3 | /// Similar to [`Discriminant`] except that it can easily printed and is 4 | /// user-defined. This trait can entirely be derived with the 5 | /// `variant_name_derive` crate. 6 | /// 7 | /// # Example 8 | /// 9 | /// ``` 10 | /// #[macro_use] extern crate variant_name_derive; 11 | /// extern crate variant_name; 12 | /// 13 | /// use variant_name::VariantName; 14 | /// 15 | /// #[derive(VariantName)] 16 | /// enum EnumTest { 17 | /// VariantA, 18 | /// VariantB { a: usize }, 19 | /// VariantC(usize), 20 | /// } 21 | /// ``` 22 | /// 23 | /// [`Discriminant`]: std::mem::Discriminant 24 | pub trait VariantName { 25 | /// Get identifier of variant 26 | fn variant_name(&self) -> &'static str; 27 | /// Get each identifier of all possible variants 28 | fn variant_names() -> &'static [&'static str]; 29 | } 30 | -------------------------------------------------------------------------------- /src/aflak_cake/tests/algo.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_cake; 2 | #[macro_use] 3 | extern crate variant_name_derive; 4 | extern crate variant_name; 5 | #[macro_use] 6 | extern crate lazy_static; 7 | 8 | #[macro_use] 9 | extern crate serde; 10 | 11 | mod support; 12 | use crate::support::*; 13 | 14 | #[test] 15 | fn test_plus1() { 16 | let plus1transform = get_plus1_transform(); 17 | 18 | let mut caller = plus1transform.start(); 19 | caller.feed(&AlgoIO::Integer(1)).unwrap(); 20 | let mut ret = caller.call(); 21 | assert_eq!(ret.next().unwrap().unwrap(), AlgoIO::Integer(2)); 22 | } 23 | 24 | #[test] 25 | fn test_autoconversions() { 26 | let divide_by_10 = get_divide_by_10_transform(); 27 | 28 | let one = AlgoIO::Integer(1); 29 | let mut caller = divide_by_10.start(); 30 | caller.feed(&one).unwrap(); 31 | let mut ret = caller.call(); 32 | assert_eq!(ret.next().unwrap().unwrap(), AlgoIO::Float(0.1)); 33 | } 34 | -------------------------------------------------------------------------------- /src/node_editor/src/scrolling.rs: -------------------------------------------------------------------------------- 1 | use crate::vec2::Vec2; 2 | 3 | #[derive(Default)] 4 | pub struct Scrolling { 5 | target: Vec2, 6 | current: Vec2, 7 | } 8 | 9 | impl Scrolling { 10 | pub fn new(target: Vec2) -> Scrolling { 11 | Scrolling { 12 | target, 13 | current: target, 14 | } 15 | } 16 | 17 | pub fn get_current(&self) -> Vec2 { 18 | self.current 19 | } 20 | 21 | /// Set target fluidly 22 | pub fn set_target(&mut self, target: Vec2) { 23 | self.target = target; 24 | } 25 | 26 | pub fn set_delta(&mut self, delta: Vec2) { 27 | self.target = self.target + delta; 28 | self.current = self.target; 29 | } 30 | 31 | /// Get current closer to target 32 | pub fn tick(&mut self) { 33 | const SPEED: f32 = 0.1; 34 | 35 | let diff = self.target - self.current; 36 | self.current = self.current + diff * SPEED; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/imgui_file_explorer/examples/test_file_explorer.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_imgui_glium_support as support; 2 | extern crate imgui; 3 | extern crate imgui_file_explorer; 4 | use imgui::*; 5 | use imgui_file_explorer::UiFileExplorer; 6 | 7 | fn test(ui: &Ui) { 8 | let window = Window::new(format!("File Explorer")) 9 | .size([600.0, 400.0], Condition::Appearing) 10 | .position([200.0, 200.0], Condition::FirstUseEver); 11 | window.build(ui, || { 12 | ui.push_item_width(-140.0); 13 | let file = ui.file_explorer("/", &["fits", "csv"]); 14 | if let Ok((Some(file), _)) = file { 15 | println!("{:?}", file); 16 | } 17 | }); 18 | } 19 | 20 | fn main() { 21 | let config = support::AppConfig { 22 | title: "Example file explorer".to_owned(), 23 | ..Default::default() 24 | }; 25 | 26 | support::init(config).main_loop(|ui, _, _| { 27 | test(ui); 28 | true 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/aflak_plot/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Plotting library for aflak. 2 | //! 3 | //! Please see the examples in the repository of this crate to get an idea of 4 | //! how it is used. 5 | //! 6 | //! Basically, this crate defines and implements two traits on imgui's `Ui` 7 | //! objet. These are [UiImage1d](plot/trait.UiImage1d.html) and 8 | //! [UiImage2d](imshow/trait.UiImage2d.html). 9 | extern crate aflak_cake as cake; 10 | extern crate aflak_primitives as primitives; 11 | extern crate glium; 12 | extern crate imgui; 13 | extern crate imgui_glium_renderer; 14 | extern crate node_editor; 15 | #[macro_use] 16 | extern crate ndarray; 17 | 18 | extern crate implot; 19 | extern crate meval; 20 | 21 | pub mod imshow; 22 | pub mod plot; 23 | pub mod scatter_lineplot; 24 | 25 | mod err; 26 | pub mod interactions; 27 | mod lims; 28 | mod ticks; 29 | mod units; 30 | mod util; 31 | 32 | pub use crate::err::Error; 33 | pub use crate::interactions::{Interaction, InteractionId, InteractionIterMut, Value, ValueIter}; 34 | pub use crate::units::AxisTransform; 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ### Changed 6 | - Update imgui to 0.0.22-pre 7 | 8 | ### Added 9 | - Show current working directory by default on file selector 10 | - Extract units and WCS from FITS files and keep them during the whole 11 | pipeline. Show units and real world coordinates on input windows. 12 | 13 | ### Fixed 14 | - Fix error in file selector 15 | - Fix error on computing texture dimension in Image2d viewer. 16 | 17 | ## [v0.0.3] - 2018-10-18 18 | 19 | - Double-feedback-loop for variables 20 | - FITS export 21 | - Node removal 22 | - Auto-layout of output windows 23 | - Show explanations for each node 24 | - Smoother scrolling on node editor 25 | - Fix constant node names after import 26 | - Miscellaneous internal improvements 27 | 28 | ## [v0.0.2] - 2018-08-11 29 | 30 | - Improve doc 31 | 32 | ## [v0.0.1] - 2018-07-04 33 | 34 | - Very first release 35 | - Tested on GNU/Linux (Ubuntu/Debian), with FITS files from the 36 | [SDSS MaNGA sky survey](http://www.sdss.org/dr14/manga/) 37 | - Windows/macOS support in progress 38 | -------------------------------------------------------------------------------- /src/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | 3 | pub const TEMPLATES: &[&str] = &[ 4 | "waveform", 5 | "equivalent_width", 6 | "fits_cleaning", 7 | "velocity_field", 8 | ]; 9 | 10 | pub fn build_cli() -> App<'static, 'static> { 11 | App::new(env!("CARGO_PKG_NAME")) 12 | .author(env!("CARGO_PKG_AUTHORS")) 13 | .about(env!("CARGO_PKG_DESCRIPTION")) 14 | .arg( 15 | Arg::with_name("fits") 16 | .short("f") 17 | .long("fits") 18 | .value_name("FITS") 19 | .help("Set a FITS file to load"), 20 | ) 21 | .arg( 22 | Arg::with_name("template") 23 | .short("t") 24 | .long("template") 25 | .value_name("TEMPLATE NAME") 26 | .possible_values(TEMPLATES) 27 | .help("The name of the template to use"), 28 | ) 29 | .arg( 30 | Arg::with_name("ron") 31 | .long("ron") 32 | .value_name("RON FILE") 33 | .conflicts_with("template") 34 | .help("Import editor from .ron file"), 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/imgui_tone_curve/examples/test_tone_curve.rs: -------------------------------------------------------------------------------- 1 | extern crate glium; 2 | extern crate imgui; 3 | extern crate imgui_glium_renderer; 4 | extern crate imgui_tone_curve; 5 | extern crate imgui_winit_support; 6 | use imgui::*; 7 | use imgui_tone_curve::{ToneCurveState, UiToneCurve}; 8 | 9 | mod support; 10 | 11 | fn test(ui: &Ui, mut state: &mut ToneCurveState) { 12 | let window = Window::new(format!("Tone Curve")) 13 | .size([600.0, 400.0], Condition::Appearing) 14 | .position([200.0, 200.0], Condition::FirstUseEver); 15 | window.build(ui, || { 16 | let draw_list = ui.get_window_draw_list(); 17 | let vectors = ui.tone_curve(&mut state, &draw_list); 18 | if let Ok(vectors) = vectors { 19 | let vectors = vectors.unwrap(); 20 | ui.text(format!("control points: {:?}", vectors.control_points())); 21 | ui.text_wrapped(&format!("data array: {:?}", vectors.array())); 22 | } 23 | }); 24 | } 25 | 26 | fn main() { 27 | let mut state = ToneCurveState::default(); 28 | support::init("imgui-tone_curve-test").main_loop(move |_run, ui, _, _| { 29 | test(ui, &mut state); 30 | true 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/release-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Quick-and-dirty convenience script for making releases. 3 | 4 | set -eux 5 | 6 | UNAME_OUT="$(uname -s)" 7 | case "$UNAME_OUT" in 8 | Linux*) 9 | OS=linux 10 | OS_PRETTY=Linux 11 | ;; 12 | Darwin*) 13 | OS=macos 14 | OS_PRETTY=macOS 15 | ;; 16 | *) echo "Unsupported OS: $UNAME_OUT"; exit 1 17 | esac 18 | 19 | REV=$(git rev-parse --short=9 HEAD) 20 | ARCHIVE_NAME="aflak-$OS-$REV.tar.gz" 21 | DOWNLOAD_LINK="https://aflak-vis.github.io/download/build/$OS/$ARCHIVE_NAME" 22 | 23 | cargo clean 24 | echo '[profile.release]' >> Cargo.toml 25 | echo 'lto = true' >> Cargo.toml 26 | cargo build --release 27 | strip target/release/aflak 28 | tar cvf - -C target/release aflak | gzip --best > "$ARCHIVE_NAME" 29 | 30 | rm -rf aflak-vis 31 | git clone --depth 1 git@github.com:aflak-vis/aflak-vis.github.io.git aflak-vis 32 | mkdir -p "aflak-vis/download/build/$OS" 33 | mv "$ARCHIVE_NAME" "aflak-vis/download/build/$OS" 34 | cd aflak-vis 35 | git add "download/build/$OS/$ARCHIVE_NAME" 36 | git commit -m "Release $REV for $OS" 37 | git push 38 | 39 | cd .. 40 | sed -i 's|^- \['"$OS_PRETTY"'\]\(.*\)$|- ['"$OS_PRETTY"']('"$DOWNLOAD_LINK"')|' ../README.md 41 | git add ../README.md 42 | git commit -m "Release $REV for $OS" 43 | -------------------------------------------------------------------------------- /src/node_editor/src/vec2.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, Sub}; 2 | 3 | #[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 4 | pub struct Vec2(pub f32, pub f32); 5 | 6 | impl From<(f32, f32)> for Vec2 { 7 | fn from(vec: (f32, f32)) -> Self { 8 | Vec2(vec.0, vec.1) 9 | } 10 | } 11 | impl From<[f32; 2]> for Vec2 { 12 | fn from(vec: [f32; 2]) -> Self { 13 | Vec2(vec[0], vec[1]) 14 | } 15 | } 16 | impl From for [f32; 2] { 17 | fn from(vec: Vec2) -> Self { 18 | [vec.0, vec.1] 19 | } 20 | } 21 | 22 | impl Vec2 { 23 | pub fn new>(t: T) -> Self { 24 | t.into() 25 | } 26 | 27 | pub fn squared_norm(self) -> f32 { 28 | self.0 * self.0 + self.1 * self.1 29 | } 30 | } 31 | 32 | impl Add for Vec2 { 33 | type Output = Vec2; 34 | fn add(self, other: Vec2) -> Vec2 { 35 | Vec2(self.0 + other.0, self.1 + other.1) 36 | } 37 | } 38 | 39 | impl Sub for Vec2 { 40 | type Output = Vec2; 41 | fn sub(self, other: Vec2) -> Vec2 { 42 | Vec2(self.0 - other.0, self.1 - other.1) 43 | } 44 | } 45 | 46 | impl Mul for Vec2 47 | where 48 | T: Into, 49 | { 50 | type Output = Vec2; 51 | fn mul(self, other: T) -> Vec2 { 52 | let other = other.into(); 53 | Vec2(self.0 * other, self.1 * other) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aflak" 3 | version = "0.0.4-pre" 4 | authors = ["Malik Olivier Boussejra "] 5 | description = "Advanced Framework for Learning Astrophysical Knowledge" 6 | edition = "2021" 7 | license = "GPL-3.0-only" 8 | homepage = "https://github.com/aflak-vis/aflak" 9 | repository = "https://github.com/aflak-vis/aflak" 10 | readme = "../README.md" 11 | build = "src/build.rs" 12 | keywords = ["programming", "visualization", "astronomy", "astrophysics", "aflak"] 13 | categories = ["visualization", "science"] 14 | 15 | [dependencies] 16 | aflak_cake = { path = "./aflak_cake", version = "0.0.3" } 17 | aflak_plot = { path = "./aflak_plot", version = "0.0.3" } 18 | aflak_primitives = { path = "./aflak_primitives", version = "0.0.3" } 19 | imgui_file_explorer = { path = "./imgui_file_explorer", version = "0.0.3" } 20 | imgui_tone_curve = { path = "./imgui_tone_curve", version = "0.0.3" } 21 | aflak_imgui_glium_support = { path = "./imgui_glium_support", version = "0.0.3" } 22 | node_editor = { path = "./node_editor", version = "0.0.3" } 23 | 24 | clap = "2.32.0" 25 | glium = { version = "0.30", default-features = true } 26 | imgui = { version = "0.8.0" } 27 | imgui-glium-renderer = { version = "0.8.0" } 28 | implot = { git = "https://github.com/4bb4/implot-rs" } 29 | owning_ref = "0.4.0" 30 | [build-dependencies] 31 | clap = "2.32.0" 32 | 33 | [workspace] 34 | members = [ 35 | "aflak_cake", 36 | "aflak_plot", 37 | "aflak_primitives", 38 | "imgui_file_explorer", 39 | "imgui_glium_support", 40 | "node_editor", 41 | "variant_name", 42 | "variant_name_derive", 43 | ] 44 | -------------------------------------------------------------------------------- /src/aflak_plot/examples/sin.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_imgui_glium_support as support; 2 | extern crate aflak_plot; 3 | extern crate imgui; 4 | extern crate ndarray; 5 | 6 | use aflak_plot::{ 7 | plot::{self, UiImage1d}, 8 | AxisTransform, 9 | }; 10 | use imgui::{Condition, Window}; 11 | 12 | use std::collections::HashMap; 13 | use std::f32; 14 | use std::path::PathBuf; 15 | 16 | use aflak_plot::imshow::cake::OutputId; 17 | use aflak_plot::plot::node_editor::NodeEditor; 18 | 19 | fn main() { 20 | let config = support::AppConfig { 21 | title: "Example sin.rs".to_owned(), 22 | ini_filename: Some(PathBuf::from("sin.ini")), 23 | ..Default::default() 24 | }; 25 | let mut state = plot::State::default(); 26 | 27 | const MAX: f32 = 4.0 * f32::consts::PI; 28 | let sin = ndarray::Array1::linspace(0.0, MAX, 100).mapv_into(f32::sin); 29 | support::init(config).main_loop(move |ui, _, _| { 30 | Window::new(format!("Sin")) 31 | .size([430.0, 450.0], Condition::FirstUseEver) 32 | .build(ui, || { 33 | ui.image1d( 34 | &sin, 35 | "sin(x)", 36 | "m", 37 | Some(&AxisTransform::new("x", "rad", |x| x / MAX)), 38 | &mut state, 39 | &mut None, 40 | &mut HashMap::new(), 41 | &mut None, 42 | OutputId::new(0), 43 | &NodeEditor::default(), 44 | ) 45 | .expect("Image1d failed"); 46 | }); 47 | true 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/aflak_cake/tests/serialize.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_cake; 2 | #[macro_use] 3 | extern crate variant_name_derive; 4 | extern crate variant_name; 5 | #[macro_use] 6 | extern crate lazy_static; 7 | 8 | extern crate ron; 9 | #[macro_use] 10 | extern crate serde; 11 | 12 | mod support; 13 | use crate::support::*; 14 | 15 | use aflak_cake::export::{DeserTransform, SerialTransform}; 16 | use aflak_cake::macros::MacroManager; 17 | use ron::de::from_str; 18 | use ron::ser::to_string; 19 | 20 | #[test] 21 | fn test_plus1() { 22 | let macro_manager = MacroManager::new(); 23 | let plus1transform = get_plus1_transform(); 24 | 25 | let s = to_string(&SerialTransform::new(&plus1transform)).unwrap(); 26 | assert_eq!("Function(\"plus1\",1,0,0,)", s); 27 | 28 | let plus1_deser: DeserTransform = from_str(&s).unwrap(); 29 | let plus1transform_back = plus1_deser.into_transform(¯o_manager).unwrap(); 30 | 31 | // Check that plus1transform_back behaves as plus1transform 32 | let mut caller = plus1transform_back.start(); 33 | caller.feed(&AlgoIO::Integer(1)).unwrap(); 34 | let mut ret = caller.call(); 35 | assert_eq!(ret.next().unwrap().unwrap(), AlgoIO::Integer(2)); 36 | 37 | let const1 = get_get1_transform(); 38 | 39 | let s = to_string(&SerialTransform::new(&const1)).unwrap(); 40 | assert_eq!("Constant(Integer(1))", s); 41 | 42 | let const1_deser: DeserTransform = from_str(&s).unwrap(); 43 | let const1_back = const1_deser.into_transform(¯o_manager).unwrap(); 44 | // Check that const1_back behaves as const1 45 | let caller = const1_back.start(); 46 | let mut ret = caller.call(); 47 | assert_eq!(ret.next().unwrap().unwrap(), AlgoIO::Integer(1)); 48 | } 49 | -------------------------------------------------------------------------------- /src/aflak_plot/src/units.rs: -------------------------------------------------------------------------------- 1 | /// Define transformation between pixel coordinates and world coordinates for 2 | /// an axis. 3 | pub struct AxisTransform<'a, F> { 4 | label: &'a str, 5 | unit: &'a str, 6 | transform: F, 7 | } 8 | 9 | impl<'a, F> AxisTransform<'a, F> { 10 | /// Make a new axis transform with the given `label`, `unit` and `transform` 11 | /// function. 12 | pub fn new(label: &'a str, unit: &'a str, transform: F) -> Self { 13 | Self { 14 | label, 15 | unit, 16 | transform, 17 | } 18 | } 19 | 20 | /// Get axis label. 21 | pub fn label(&self) -> &str { 22 | &self.label 23 | } 24 | 25 | /// Get axis unit. 26 | pub fn unit(&self) -> &str { 27 | &self.unit 28 | } 29 | 30 | /// Get axis name (label and unit). 31 | pub fn name(&self) -> String { 32 | match (self.label, self.unit) { 33 | ("", "") => String::new(), 34 | (label, "") => label.to_owned(), 35 | ("", unit) => unit.to_owned(), 36 | (label, unit) => format!("{} ({})", label, unit), 37 | } 38 | } 39 | } 40 | 41 | impl<'a, T> AxisTransform<'a, fn(T) -> T> { 42 | /// Get the identity transformation. Keep pixel coordinates. 43 | pub fn id(label: &'a str, unit: &'a str) -> Self { 44 | fn id(x: T) -> T { 45 | x 46 | } 47 | Self { 48 | label, 49 | unit, 50 | transform: id, 51 | } 52 | } 53 | 54 | /// Convenience function to get a None value. 55 | pub fn none() -> Option<&'static Self> { 56 | None 57 | } 58 | } 59 | 60 | impl<'a, F: Fn(f32) -> f32> AxisTransform<'a, F> { 61 | /// Convert pixel to world coordinates. 62 | pub fn pix2world(&self, p: f32) -> f32 { 63 | (self.transform)(p) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/src/build.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | 3 | use std::env; 4 | use std::error::Error; 5 | use std::fs; 6 | use std::io::Write; 7 | use std::path::PathBuf; 8 | use std::process::Command; 9 | 10 | use clap::Shell; 11 | 12 | mod cli; 13 | 14 | fn main() { 15 | let outdir = match env::var_os("OUT_DIR") { 16 | None => return, 17 | Some(outdir) => outdir, 18 | }; 19 | let mut app = cli::build_cli(); 20 | app.gen_completions(env!("CARGO_PKG_NAME"), Shell::Bash, &outdir); 21 | 22 | fs::File::create(PathBuf::from(outdir).join("commit-info.txt")) 23 | .expect("Could not create commit-info.txt file") 24 | .write_all(commit_info().as_bytes()) 25 | .expect("Could not write to commit-info.txt"); 26 | } 27 | 28 | // Try to get hash and date of the last commit on a best effort basis. If anything goes wrong 29 | // (git not installed or if this is not a git repository) just return an empty string. 30 | // Thanks rustup for the reference implementation: 31 | // https://github.com/rust-lang-nursery/rustup.rs/blob/dd51ab0/build.rs 32 | fn commit_info() -> String { 33 | match (commit_hash(), commit_date()) { 34 | (Ok(hash), Ok(date)) => format!(" ({} {})", hash.trim_end(), date), 35 | _ => String::new(), 36 | } 37 | } 38 | 39 | fn commit_hash() -> Result { 40 | Ok(String::from_utf8( 41 | Command::new("git") 42 | .args(&["rev-parse", "--short=9", "HEAD"]) 43 | .output()? 44 | .stdout, 45 | )?) 46 | } 47 | 48 | fn commit_date() -> Result { 49 | Ok(String::from_utf8( 50 | Command::new("git") 51 | .args(&["log", "-1", "--date=short", "--pretty=format:%cd"]) 52 | .output()? 53 | .stdout, 54 | )?) 55 | } 56 | 57 | struct Ignore; 58 | 59 | impl From for Ignore 60 | where 61 | E: Error, 62 | { 63 | fn from(_: E) -> Ignore { 64 | Ignore 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/aflak_plot/src/imshow/hist.rs: -------------------------------------------------------------------------------- 1 | use ndarray::{ArrayBase, Axis, Data, Ix2, Ix3}; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub struct Bin { 5 | pub start: f32, 6 | pub end: f32, 7 | pub count: usize, 8 | } 9 | 10 | pub fn histogram(data: &ArrayBase, min: f32, max: f32) -> Vec 11 | where 12 | S: Data, 13 | { 14 | const HISTOGRAM_BIN_COUNT: usize = 100; 15 | let mut bins = Vec::with_capacity(HISTOGRAM_BIN_COUNT); 16 | let size = (max - min) / HISTOGRAM_BIN_COUNT as f32; 17 | for i in 0..HISTOGRAM_BIN_COUNT { 18 | bins.push(Bin { 19 | start: min + i as f32 * size, 20 | end: min + (i + 1) as f32 * size, 21 | count: 0, 22 | }); 23 | } 24 | 25 | for val in data.iter() { 26 | let i = (*val - min) / (max - min) * HISTOGRAM_BIN_COUNT as f32; 27 | let mut i = i as usize; 28 | if i >= HISTOGRAM_BIN_COUNT { 29 | i = HISTOGRAM_BIN_COUNT - 1; 30 | } 31 | bins[i].count += 1; 32 | } 33 | bins 34 | } 35 | 36 | pub fn histogram_color(data: &ArrayBase, min: f32, max: f32) -> Vec<[Bin; 3]> 37 | where 38 | S: Data, 39 | { 40 | const HISTOGRAM_BIN_COUNT: usize = 256; 41 | let mut bins = Vec::with_capacity(HISTOGRAM_BIN_COUNT); 42 | let size = (max - min) / HISTOGRAM_BIN_COUNT as f32; 43 | for i in 0..HISTOGRAM_BIN_COUNT { 44 | bins.push( 45 | [Bin { 46 | start: min + i as f32 * size, 47 | end: min + (i + 1) as f32 * size, 48 | count: 0, 49 | }; 3], 50 | ); 51 | } 52 | 53 | let mut channel = 0; 54 | for data in data.axis_iter(Axis(0)) { 55 | for val in data.iter() { 56 | let i = (*val - min) / (max - min) * HISTOGRAM_BIN_COUNT as f32; 57 | let mut i = i as usize; 58 | if i >= HISTOGRAM_BIN_COUNT { 59 | i = HISTOGRAM_BIN_COUNT - 1; 60 | } 61 | bins[i][channel].count += 1; 62 | } 63 | channel += 1; 64 | } 65 | bins 66 | } 67 | -------------------------------------------------------------------------------- /src/aflak_plot/examples/gradient.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_imgui_glium_support as support; 2 | extern crate aflak_plot; 3 | extern crate glium; 4 | extern crate imgui; 5 | extern crate ndarray; 6 | 7 | use std::collections::HashMap; 8 | use std::path::PathBuf; 9 | use std::time::Instant; 10 | 11 | use aflak_plot::{ 12 | imshow::{self, UiImage2d}, 13 | AxisTransform, 14 | }; 15 | 16 | use imshow::cake::OutputId; 17 | use imshow::node_editor::NodeEditor; 18 | 19 | fn main() { 20 | let config = support::AppConfig { 21 | title: "Example".to_owned(), 22 | ini_filename: Some(PathBuf::from("gradient.ini")), 23 | ..Default::default() 24 | }; 25 | let mut state = imshow::State::default(); 26 | let texture_id = imgui::TextureId::from(1); 27 | support::init(config).main_loop(move |ui, gl_ctx, textures| { 28 | if state.image_created_on().is_none() { 29 | let image_data = { 30 | const WIDTH: usize = 100; 31 | const HEIGHT: usize = 100; 32 | let mut image_data = Vec::with_capacity(WIDTH * HEIGHT); 33 | for j in 0..WIDTH { 34 | for i in 0..HEIGHT { 35 | image_data.push((i + j) as f32); 36 | } 37 | } 38 | ndarray::ArrayD::from_shape_vec(vec![WIDTH, HEIGHT], image_data).unwrap() 39 | }; 40 | state 41 | .set_image(image_data, Instant::now(), gl_ctx, texture_id, textures) 42 | .unwrap(); 43 | } 44 | imgui::Window::new(format!("Gradient")).build(ui, || { 45 | ui.image2d( 46 | gl_ctx, 47 | textures, 48 | texture_id, 49 | "gradient value", 50 | Some(&AxisTransform::new("X Axis", "m", |x| x)), 51 | Some(&AxisTransform::new("Y Axis", "m", |y| y)), 52 | &mut state, 53 | &mut None, 54 | &mut HashMap::new(), 55 | &mut None, 56 | OutputId::new(0), 57 | &NodeEditor::default(), 58 | ) 59 | .expect("Image2d failed"); 60 | }); 61 | true 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/aflak_plot/examples/wide_rectangle.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_imgui_glium_support as support; 2 | extern crate aflak_plot; 3 | extern crate glium; 4 | extern crate imgui; 5 | extern crate ndarray; 6 | 7 | use std::collections::HashMap; 8 | use std::path::PathBuf; 9 | use std::time::Instant; 10 | 11 | use aflak_plot::{ 12 | imshow::{self, UiImage2d}, 13 | AxisTransform, 14 | }; 15 | 16 | use imshow::cake::OutputId; 17 | use imshow::node_editor::NodeEditor; 18 | 19 | fn main() { 20 | let config = support::AppConfig { 21 | title: "Example wide_rectangle.rs".to_owned(), 22 | ini_filename: Some(PathBuf::from("wide_rectangle.ini")), 23 | ..Default::default() 24 | }; 25 | let mut state = imshow::State::default(); 26 | let texture_id = imgui::TextureId::from(1); 27 | support::init(config).main_loop(move |ui, gl_ctx, textures| { 28 | if state.image_created_on().is_none() { 29 | let image_data = { 30 | const WIDTH: usize = 20; 31 | const HEIGHT: usize = 10; 32 | let mut image_data = Vec::with_capacity(WIDTH * HEIGHT); 33 | for i in 0..HEIGHT { 34 | for _ in 0..WIDTH { 35 | image_data.push(i as f32); 36 | } 37 | } 38 | ndarray::ArrayD::from_shape_vec(vec![HEIGHT, WIDTH], image_data).unwrap() 39 | }; 40 | state 41 | .set_image(image_data, Instant::now(), gl_ctx, texture_id, textures) 42 | .unwrap(); 43 | } 44 | 45 | imgui::Window::new(format!("Wide Rectangle")).build(ui, || { 46 | ui.image2d( 47 | gl_ctx, 48 | textures, 49 | texture_id, 50 | "pixel", 51 | Some(&AxisTransform::new("X Axis", "m", |x| x)), 52 | Some(&AxisTransform::new("Y Axis", "m", |y| y)), 53 | &mut state, 54 | &mut None, 55 | &mut HashMap::new(), 56 | &mut None, 57 | OutputId::new(0), 58 | &NodeEditor::default(), 59 | ) 60 | .expect("Image2d failed"); 61 | }); 62 | true 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /src/aflak_plot/examples/tall_rectangle.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_imgui_glium_support as support; 2 | extern crate aflak_plot; 3 | extern crate glium; 4 | extern crate imgui; 5 | extern crate ndarray; 6 | 7 | use std::collections::HashMap; 8 | use std::path::PathBuf; 9 | use std::time::Instant; 10 | 11 | use aflak_plot::{ 12 | imshow::{self, UiImage2d}, 13 | AxisTransform, 14 | }; 15 | 16 | use imshow::cake::OutputId; 17 | use imshow::node_editor::NodeEditor; 18 | 19 | fn main() { 20 | let config = support::AppConfig { 21 | title: "Example tall_rectangle.rs".to_owned(), 22 | ini_filename: Some(PathBuf::from("tall_rectangle.ini")), 23 | ..Default::default() 24 | }; 25 | let mut state = imshow::State::default(); 26 | let texture_id = imgui::TextureId::from(1); 27 | support::init(config).main_loop(move |ui, gl_ctx, textures| { 28 | if state.image_created_on().is_none() { 29 | let image_data = { 30 | const WIDTH: usize = 10; 31 | const HEIGHT: usize = 20; 32 | let mut image_data = Vec::with_capacity(WIDTH * HEIGHT); 33 | for _ in 0..HEIGHT { 34 | for i in 0..WIDTH { 35 | image_data.push(i as f32); 36 | } 37 | } 38 | ndarray::ArrayD::from_shape_vec(vec![HEIGHT, WIDTH], image_data).unwrap() 39 | }; 40 | state 41 | .set_image(image_data, Instant::now(), gl_ctx, texture_id, textures) 42 | .unwrap(); 43 | } 44 | imgui::Window::new(format!("Tall Rectangle")).build(ui, || { 45 | ui.image2d( 46 | gl_ctx, 47 | textures, 48 | imgui::TextureId::from(1), 49 | "pixel", 50 | Some(&AxisTransform::new("X Axis", "m", |x| x)), 51 | Some(&AxisTransform::new("Y Axis", "m", |y| y)), 52 | &mut state, 53 | &mut None, 54 | &mut HashMap::new(), 55 | &mut None, 56 | OutputId::new(0), 57 | &NodeEditor::default(), 58 | ) 59 | .expect("Image2d failed"); 60 | }); 61 | true 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/src/layout.rs: -------------------------------------------------------------------------------- 1 | use imgui::ImString; 2 | 3 | pub struct LayoutEngine { 4 | outputs: Vec, 5 | } 6 | 7 | const EDITOR_WINDOW_DEFAULT_POSITION: [f32; 2] = [10.0, 10.0]; 8 | const EDITOR_WINDOW_DEFAULT_SIZE: (f32, f32) = (1200.0, 800.0); 9 | 10 | const OUTPUT_WINDOW_DEFAULT_SIZE: [f32; 2] = [600.0, 400.0]; 11 | const OUTPUT_WINDOW_DEFAULT_MARGIN: f32 = 50.0; 12 | 13 | pub struct Layout { 14 | pub position: [f32; 2], 15 | pub size: [f32; 2], 16 | } 17 | 18 | impl LayoutEngine { 19 | pub fn new() -> LayoutEngine { 20 | LayoutEngine { outputs: vec![] } 21 | } 22 | 23 | pub fn default_editor_layout(&self, display_size: [f32; 2]) -> Layout { 24 | let position = EDITOR_WINDOW_DEFAULT_POSITION; 25 | let default_size = EDITOR_WINDOW_DEFAULT_SIZE; 26 | let size = [ 27 | default_size.0.min(display_size[0] - position[0]), 28 | default_size.1.min(default_size.1 - position[1]), 29 | ]; 30 | Layout { position, size } 31 | } 32 | 33 | /// Align windows on a 4-column-wide grid 34 | /// Return None if a window with the given name already exists. 35 | pub fn default_output_window_layout( 36 | &mut self, 37 | name: &ImString, 38 | display_size: [f32; 2], 39 | ) -> Option { 40 | if self.outputs.contains(name) { 41 | None 42 | } else { 43 | let (row, col) = { 44 | let n = self.outputs.len(); 45 | let row = n / 4; 46 | let col = n % 4; 47 | (row as f32, col as f32) 48 | }; 49 | self.outputs.push(name.clone()); 50 | let pos_x = (EDITOR_WINDOW_DEFAULT_POSITION[0] 51 | + col * (OUTPUT_WINDOW_DEFAULT_SIZE[0] + OUTPUT_WINDOW_DEFAULT_MARGIN)) 52 | % display_size[0]; 53 | let pos_y = (EDITOR_WINDOW_DEFAULT_POSITION[1] 54 | + EDITOR_WINDOW_DEFAULT_SIZE.1 55 | + OUTPUT_WINDOW_DEFAULT_MARGIN 56 | + row * (OUTPUT_WINDOW_DEFAULT_SIZE[1] + OUTPUT_WINDOW_DEFAULT_MARGIN)) 57 | % display_size[1]; 58 | Some(Layout { 59 | position: [pos_x, pos_y], 60 | size: OUTPUT_WINDOW_DEFAULT_SIZE, 61 | }) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/node_editor/src/export.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | use std::io; 4 | use std::sync::Arc; 5 | 6 | use crate::cake; 7 | use ron::{de, error as ser_error}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub enum ExportError { 11 | SerializationError(ser_error::Error), 12 | IOError(Arc), 13 | } 14 | 15 | impl fmt::Display for ExportError { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | match *self { 18 | ExportError::SerializationError(ref e) => write!(f, "Serialization error! {}", e), 19 | ExportError::IOError(ref e) => write!(f, "I/O error! {}", e), 20 | } 21 | } 22 | } 23 | 24 | impl error::Error for ExportError { 25 | fn description(&self) -> &'static str { 26 | "ExportError" 27 | } 28 | } 29 | 30 | impl From for ExportError { 31 | fn from(io_error: io::Error) -> Self { 32 | ExportError::IOError(Arc::new(io_error)) 33 | } 34 | } 35 | 36 | impl From for ExportError { 37 | fn from(serial_error: ser_error::Error) -> Self { 38 | ExportError::SerializationError(serial_error) 39 | } 40 | } 41 | 42 | #[derive(Debug, Clone)] 43 | pub enum ImportError { 44 | DSTError(cake::ImportError), 45 | DeserializationError(de::Error), 46 | IOError(Arc), 47 | } 48 | 49 | impl fmt::Display for ImportError { 50 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 51 | match *self { 52 | ImportError::DSTError(ref e) => write!(f, "Error while building DST! {}", e), 53 | ImportError::DeserializationError(ref e) => write!(f, "Deserialization error! {}", e), 54 | ImportError::IOError(ref e) => write!(f, "I/O error! {}", e), 55 | } 56 | } 57 | } 58 | 59 | impl error::Error for ImportError { 60 | fn description(&self) -> &'static str { 61 | "ImportError" 62 | } 63 | } 64 | 65 | impl From for ImportError { 66 | fn from(io_error: io::Error) -> Self { 67 | ImportError::IOError(Arc::new(io_error)) 68 | } 69 | } 70 | 71 | impl From for ImportError { 72 | fn from(deserial_error: de::Error) -> Self { 73 | ImportError::DeserializationError(deserial_error) 74 | } 75 | } 76 | 77 | impl From for ImportError { 78 | fn from(e: cake::ImportError) -> Self { 79 | ImportError::DSTError(e) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/aflak_plot/examples/peak.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_imgui_glium_support as support; 2 | extern crate aflak_plot; 3 | extern crate glium; 4 | extern crate imgui; 5 | extern crate ndarray; 6 | 7 | use std::collections::HashMap; 8 | use std::path::PathBuf; 9 | use std::time::Instant; 10 | 11 | use aflak_plot::{ 12 | imshow::{self, UiImage2d}, 13 | AxisTransform, 14 | }; 15 | 16 | use imshow::cake::OutputId; 17 | use imshow::node_editor::NodeEditor; 18 | 19 | fn main() { 20 | let config = support::AppConfig { 21 | title: "Example peak.rs".to_owned(), 22 | ini_filename: Some(PathBuf::from("peak.ini")), 23 | ..Default::default() 24 | }; 25 | let mut state = imshow::State::default(); 26 | let texture_id = imgui::TextureId::from(1); 27 | support::init(config).main_loop(move |ui, gl_ctx, textures| { 28 | if state.image_created_on().is_none() { 29 | const WIDTH: usize = 200; 30 | const HEIGHT: usize = 100; 31 | let image_data = ndarray::Array2::from_shape_fn([HEIGHT, WIDTH], |(j, i)| { 32 | use std::f32; 33 | let i = i as isize; 34 | let j = j as isize; 35 | let width = WIDTH as isize; 36 | let height = HEIGHT as isize; 37 | let sin = f32::sin((i - width / 3) as f32 / WIDTH as f32 * 2.0 * f32::consts::PI); 38 | let cos = f32::cos((j - height / 3) as f32 / HEIGHT as f32 * 2.0 * f32::consts::PI); 39 | f32::exp(sin * sin + cos * cos) 40 | }) 41 | .into_dimensionality() 42 | .unwrap(); 43 | state 44 | .set_image(image_data, Instant::now(), gl_ctx, texture_id, textures) 45 | .unwrap(); 46 | } 47 | imgui::Window::new(format!("Peak")).build(ui, || { 48 | ui.image2d( 49 | gl_ctx, 50 | textures, 51 | texture_id, 52 | "exp(sin(x)^2 + cos(y)^2)", 53 | Some(&AxisTransform::new("X Axis", "m", |x| x)), 54 | Some(&AxisTransform::new("Y Axis", "m", |y| y)), 55 | &mut state, 56 | &mut None, 57 | &mut HashMap::new(), 58 | &mut None, 59 | OutputId::new(0), 60 | &NodeEditor::default(), 61 | ) 62 | .expect("Image2d failed"); 63 | }); 64 | true 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /src/aflak_primitives/src/roi.rs: -------------------------------------------------------------------------------- 1 | use ndarray::ArrayView2; 2 | 3 | /// A region of interest in a 2D image. 4 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 5 | pub enum ROI { 6 | /// The whole image is a region of interest. 7 | All, 8 | /// The list of pixels selected by this region of interest. 9 | PixelList(Vec<(usize, usize)>), 10 | } 11 | 12 | impl ROI { 13 | /// Get the value of each point of the 2D image in the region of interest, 14 | /// along with the original coordinate of each selected pixel. 15 | pub fn filter(&self, data: ArrayView2) -> Vec<((usize, usize), f32)> { 16 | match *self { 17 | ROI::All => { 18 | let mut out = Vec::with_capacity(data.len()); 19 | let size = data.dim(); 20 | for j in 0..size.1 { 21 | for i in 0..size.0 { 22 | out.push(((i, j), *data.get([j, i]).unwrap())); 23 | } 24 | } 25 | out 26 | } 27 | ROI::PixelList(ref pixels) => { 28 | let mut out = Vec::with_capacity(pixels.len()); 29 | for &(i, j) in pixels { 30 | if let Some(val) = data.get([j, i]) { 31 | out.push(((i, j), *val)); 32 | } 33 | } 34 | out 35 | } 36 | } 37 | } 38 | 39 | pub fn filter_upside_down(&self, data: ArrayView2) -> Vec<((usize, usize), f32)> { 40 | let dim = data.dim(); 41 | match *self { 42 | ROI::All => { 43 | let mut out = Vec::with_capacity(data.len()); 44 | let size = data.dim(); 45 | for j in 0..size.1 { 46 | for i in 0..size.0 { 47 | out.push(((i, j), *data.get([i, j]).unwrap())); 48 | } 49 | } 50 | out 51 | } 52 | ROI::PixelList(ref pixels) => { 53 | let mut out = Vec::with_capacity(pixels.len()); 54 | for &(i, j) in pixels { 55 | if let Some(val) = data.get([(dim.0 - 1) - j, i]) { 56 | out.push(((i, j), *val)); 57 | } 58 | } 59 | out 60 | } 61 | } 62 | } 63 | 64 | pub fn datalen(&self) -> usize { 65 | match *self { 66 | ROI::All => 0, 67 | ROI::PixelList(ref pixels) => pixels.len(), 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/aflak_plot/src/scatter_lineplot/mod.rs: -------------------------------------------------------------------------------- 1 | //! Draw scatter_plot. 2 | mod state; 3 | 4 | use imgui::Ui; 5 | use implot::PlotUi; 6 | use ndarray::{ArrayBase, Data, Ix2}; 7 | use std::collections::HashMap; 8 | 9 | pub use self::interactions::InteractionId; 10 | use super::interactions; 11 | use super::AxisTransform; 12 | use super::Error; 13 | use crate::imshow::cake::{OutputId, TransformIdx}; 14 | type EditabaleValues = HashMap; 15 | pub use self::state::State; 16 | 17 | /// Implementation of a UI to visualize a 1D image with ImGui using a plot. 18 | pub trait UiScatter { 19 | fn scatter( 20 | &self, 21 | image: &ArrayBase, 22 | plot_ui: &PlotUi, 23 | xaxis: Option<&AxisTransform>, 24 | yaxis: Option<&AxisTransform>, 25 | state: &mut State, 26 | copying: &mut Option<(InteractionId, TransformIdx)>, 27 | store: &mut EditabaleValues, 28 | attaching: &mut Option<(OutputId, TransformIdx, usize)>, 29 | outputid: OutputId, 30 | ) -> Result<(), Error> 31 | where 32 | S: Data, 33 | FX: Fn(f32) -> f32, 34 | FY: Fn(f32) -> f32; 35 | } 36 | 37 | impl<'ui> UiScatter for Ui<'ui> { 38 | /// Draw a plot in the remaining space of the window. 39 | /// 40 | /// The mutable reference `state` contains the current state of the user 41 | /// interaction with the window. 42 | fn scatter( 43 | &self, 44 | image: &ArrayBase, 45 | plot_ui: &PlotUi, 46 | xaxis: Option<&AxisTransform>, 47 | yaxis: Option<&AxisTransform>, 48 | state: &mut State, 49 | copying: &mut Option<(InteractionId, TransformIdx)>, 50 | store: &mut EditabaleValues, 51 | attaching: &mut Option<(OutputId, TransformIdx, usize)>, 52 | outputid: OutputId, 53 | ) -> Result<(), Error> 54 | where 55 | S: Data, 56 | FX: Fn(f32) -> f32, 57 | FY: Fn(f32) -> f32, 58 | { 59 | let p = self.cursor_screen_pos(); 60 | let window_pos = self.window_pos(); 61 | let window_size = self.window_size(); 62 | let size = [window_size[0], window_size[1] - (p[1] - window_pos[1])]; 63 | state.simple_plot( 64 | self, 65 | image, 66 | &plot_ui, 67 | xaxis, 68 | yaxis, 69 | size, 70 | &mut *copying, 71 | &mut *store, 72 | &mut *attaching, 73 | outputid, 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/variant_name_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate syn; 3 | 4 | #[macro_use] 5 | extern crate quote; 6 | 7 | use proc_macro::TokenStream; 8 | use syn::DeriveInput; 9 | 10 | #[proc_macro_derive(VariantName)] 11 | pub fn variant_name(input: TokenStream) -> TokenStream { 12 | // Parse the input tokens into a syntax tree 13 | let input: DeriveInput = syn::parse(input).unwrap(); 14 | 15 | let name = &input.ident; 16 | 17 | // Build the output, possibly using quasi-quotation 18 | let expanded = match input.data { 19 | syn::Data::Struct(_) => { 20 | quote! { 21 | impl VariantName for #name { 22 | fn variant_name(&self) -> &'static str { 23 | stringify!(#name) 24 | } 25 | fn variant_names() -> &'static [&'static str] { 26 | &[stringify!(#name)] 27 | } 28 | } 29 | } 30 | } 31 | syn::Data::Enum(enu) => { 32 | let branches = enu.variants.iter().map(|v| { 33 | let ident = &v.ident; 34 | let match_identifier = match v.fields { 35 | syn::Fields::Named(_) => { 36 | quote! { 37 | #ident{ .. } 38 | } 39 | } 40 | syn::Fields::Unnamed(_) => { 41 | quote! { 42 | #ident( .. ) 43 | } 44 | } 45 | syn::Fields::Unit => { 46 | quote! { 47 | #ident 48 | } 49 | } 50 | }; 51 | quote! { 52 | &#name::#match_identifier => stringify!(#ident) 53 | } 54 | }); 55 | let names = enu.variants.iter().map(|v| v.ident); 56 | quote! { 57 | impl VariantName for #name { 58 | fn variant_name(&self) -> &'static str { 59 | match self { 60 | #(#branches),* 61 | } 62 | } 63 | fn variant_names() -> &'static [&'static str] { 64 | &[#(stringify!(#names)),*] 65 | } 66 | } 67 | } 68 | } 69 | syn::Data::Union(_) => panic!("VariantName can only be derived for Enum or Struct!"), 70 | }; 71 | 72 | // Hand the output tokens back to the compiler 73 | expanded.into() 74 | } 75 | -------------------------------------------------------------------------------- /src/aflak_cake/TODO: -------------------------------------------------------------------------------- 1 | ## Transformation 2 | 3 | - [x] Do not store input and output directly within it. Indeed method to 4 | compute input and output depend on the type of the transformation (function, 5 | constant or macro in the future). The input and output fields are thus 6 | obsolete and should not be used. 7 | 8 | - [x] Same: Description and name should be a property of Algorithm. Constants 9 | do have a stored description, and macro too, however it should be retrieved 10 | from the Algorithm property. 11 | 12 | - [ ] Add name for each input/output. This should be a property of 13 | Algorithm::Function, not Transformation 14 | 15 | 16 | In the end, replacing Transformation by an Enumeration may be a good idea. 17 | 18 | 19 | ## DST 20 | 21 | - [x] Implement compute_output as a future(-like) within aflak_cake, not in 22 | node_editor as it is now. 23 | - [x] Each Transformation contains an Instant for its time of update 24 | update (influencing compute results). 25 | - [x] Each MetaTransform contains an Instant for its time of update, 26 | this includes new connections and updating of default values. 27 | - [x] A MetaTransform's Instant is defined as 28 | max(Transformation::updated_on, MetaTransform::updated_on) 29 | - [x] A TransformIdx's Instant is defined as max(all its parents's instants). 30 | - [x] Make DST clonable -> DST should not contain very big objects. 31 | Big objects are found in the cache. 32 | - [x] For each task, clone and send DST to a thread, along with a pointer to 33 | the cache. 34 | - [x] struct Cache { 35 | cache: HashMap>)>>, 36 | in_use: AtomicIsize, 37 | scheduled_for_destruction: AtomicBool, 38 | } 39 | - [x] Always get cached value as Arc, use it then drop it when the user 40 | program does not need it anymore. 41 | - [x] Computing should be done as: do nothing if scheduled_for_destruction is true. If false, increment cache.in_use and send &RwLock<_> 42 | to worker, then 43 | Get cache lock 44 | | 45 | If cache is up to date (cache.instant() >= transform.instant()) 46 | -> Return Arc 47 | Else 48 | -> Remove cache then re-compute it while keeping the lock, to prevent other 49 | | threads from doing the same computing in parallel and wasting CPU. 50 | | 51 | cache.instant := max(cache.instant, transform.instant) for all cache and 52 | | transform met in the computation above Check if cache is 53 | | still up to date 54 | Return Arc and release lock 55 | | 56 | Decrement cache.in_use . 57 | - [x] On dropping Cache, set scheduled_for_destruction to true then wait for in_use to be 0 before dropping the HashMap. 58 | -------------------------------------------------------------------------------- /src/aflak_cake/src/future.rs: -------------------------------------------------------------------------------- 1 | use futures::{Async, Future, Poll}; 2 | use rayon; 3 | 4 | use std::mem; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | /// An asynchronous task 8 | pub struct Task { 9 | state: Arc>>, 10 | } 11 | 12 | enum TaskState { 13 | Ready(T), 14 | NotReady, 15 | Consumed, 16 | Errored(E), 17 | } 18 | 19 | impl Task { 20 | /// Make a new task that is already finished and successful. 21 | pub fn resolved(t: T) -> Self { 22 | Self { 23 | state: Arc::new(Mutex::new(TaskState::Ready(t))), 24 | } 25 | } 26 | 27 | /// Make a new task that is already finished and failed. 28 | pub fn errored(e: E) -> Self { 29 | Self { 30 | state: Arc::new(Mutex::new(TaskState::Errored(e))), 31 | } 32 | } 33 | } 34 | 35 | impl Task 36 | where 37 | T: Sync + Send + 'static, 38 | E: Send + 'static, 39 | { 40 | /// Make a new asynchronous task from closure. 41 | pub fn new(f: F) -> Self 42 | where 43 | F: FnOnce() -> Result + Send + 'static, 44 | { 45 | let state = Arc::new(Mutex::new(TaskState::NotReady)); 46 | let passed_state = state.clone(); 47 | 48 | rayon::spawn(move || { 49 | let r = f(); 50 | let mut lock = passed_state.lock().unwrap(); 51 | *lock = match r { 52 | Ok(t) => TaskState::Ready(t), 53 | Err(e) => TaskState::Errored(e), 54 | }; 55 | }); 56 | Self { state } 57 | } 58 | } 59 | 60 | impl Future for Task { 61 | type Item = T; 62 | type Error = E; 63 | 64 | fn poll(&mut self) -> Poll { 65 | match self.state.lock() { 66 | Ok(mut lock) => { 67 | if let TaskState::Ready(_) = *lock { 68 | let ready_state = mem::replace(&mut *lock, TaskState::Consumed); 69 | if let TaskState::Ready(t) = ready_state { 70 | Ok(Async::Ready(t)) 71 | } else { 72 | unreachable!() 73 | } 74 | } else if let TaskState::Errored(_) = *lock { 75 | let errored_state = mem::replace(&mut *lock, TaskState::Consumed); 76 | if let TaskState::Errored(e) = errored_state { 77 | Err(e) 78 | } else { 79 | unreachable!() 80 | } 81 | } else { 82 | Ok(Async::NotReady) 83 | } 84 | } 85 | Err(_) => { 86 | // TODO: Handle cleaning poison error 87 | panic!("Poison error") 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/aflak_cake/src/timed.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::Deref; 3 | use std::time::Instant; 4 | 5 | /// A smart pointer embedding the instant the contained value was created. 6 | /// 7 | /// # Example 8 | /// 9 | /// ```rust 10 | /// extern crate aflak_cake; 11 | /// use aflak_cake::Timed; 12 | /// 13 | /// let timed_value = Timed::from(0); 14 | /// // Do stuff with timed value 15 | /// println!("{}", *timed_value + 1); 16 | /// // Print the instant on which the value was created on the line above 17 | /// println!("{:?}", Timed::created_on(&timed_value)); 18 | /// ``` 19 | #[derive(Debug, Copy, Clone)] 20 | pub struct Timed { 21 | value: T, 22 | created_on: Instant, 23 | } 24 | 25 | impl fmt::Display for Timed { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | write!( 28 | f, 29 | "{} (updated {:?} ago)", 30 | self.value, 31 | self.created_on.elapsed() 32 | ) 33 | } 34 | } 35 | 36 | impl Deref for Timed { 37 | type Target = T; 38 | fn deref(&self) -> &T { 39 | &self.value 40 | } 41 | } 42 | 43 | impl Timed { 44 | /// Create a new value assumed to have been created at instant `created_on`. 45 | pub fn from_instant(t: T, created_on: Instant) -> Self { 46 | Self { 47 | value: t, 48 | created_on, 49 | } 50 | } 51 | 52 | /// Extract contained value. 53 | pub fn take(t: Self) -> T { 54 | t.value 55 | } 56 | 57 | /// Get instant on which the value was created. 58 | pub fn created_on(t: &Self) -> Instant { 59 | t.created_on 60 | } 61 | 62 | /// Extract values from a Result of timed values. 63 | pub fn take_from_result(result: Result, Timed>) -> Result { 64 | result.map(Timed::take).map_err(Timed::take) 65 | } 66 | 67 | /// Change the contained value by applying the function `f`. This function 68 | /// does not alter the instant the value was created. 69 | pub fn map U>(t: Timed, f: F) -> Timed { 70 | Timed { 71 | value: f(t.value), 72 | created_on: t.created_on, 73 | } 74 | } 75 | } 76 | 77 | impl Timed> { 78 | /// Convert a timed result to a result of times values. 79 | pub fn map_result(result: Timed>) -> Result, Timed> { 80 | let created_on = result.created_on; 81 | match result.value { 82 | Ok(t) => Ok(Timed::from_instant(t, created_on)), 83 | Err(e) => Err(Timed::from_instant(e, created_on)), 84 | } 85 | } 86 | } 87 | 88 | impl From for Timed { 89 | fn from(t: T) -> Self { 90 | Self { 91 | value: t, 92 | created_on: Instant::now(), 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/aflak_plot/src/plot/mod.rs: -------------------------------------------------------------------------------- 1 | //! Draw plots. 2 | mod state; 3 | 4 | use imgui::Ui; 5 | use ndarray::{ArrayBase, Data, Ix1}; 6 | use std::collections::HashMap; 7 | 8 | extern crate aflak_cake as cake; 9 | pub extern crate aflak_primitives as primitives; 10 | pub extern crate node_editor; 11 | use super::interactions; 12 | use super::lims; 13 | use super::ticks; 14 | use super::util; 15 | use super::AxisTransform; 16 | use super::Error; 17 | 18 | pub use self::cake::TransformIdx; 19 | pub use self::interactions::InteractionId; 20 | pub use self::state::State; 21 | use super::node_editor::NodeEditor; 22 | use super::primitives::{IOErr, IOValue}; 23 | 24 | type EditableValues = HashMap; 25 | type AflakNodeEditor = NodeEditor; 26 | 27 | /// Implementation of a UI to visualize a 1D image with ImGui using a plot. 28 | pub trait UiImage1d { 29 | fn image1d( 30 | &self, 31 | image: &ArrayBase, 32 | vtype: &str, 33 | vunit: &str, 34 | axis: Option<&AxisTransform>, 35 | state: &mut State, 36 | copying: &mut Option<(InteractionId, TransformIdx)>, 37 | store: &mut EditableValues, 38 | attaching: &mut Option<(cake::OutputId, TransformIdx, usize)>, 39 | outputid: cake::OutputId, 40 | node_editor: &AflakNodeEditor, 41 | ) -> Result<(), Error> 42 | where 43 | S: Data, 44 | F: Fn(f32) -> f32; 45 | } 46 | 47 | impl<'ui> UiImage1d for Ui<'ui> { 48 | /// Draw a plot in the remaining space of the window. 49 | /// 50 | /// The mutable reference `state` contains the current state of the user 51 | /// interaction with the window. 52 | fn image1d( 53 | &self, 54 | image: &ArrayBase, 55 | vtype: &str, 56 | vunit: &str, 57 | axis: Option<&AxisTransform>, 58 | state: &mut State, 59 | copying: &mut Option<(InteractionId, TransformIdx)>, 60 | store: &mut EditableValues, 61 | attaching: &mut Option<(cake::OutputId, TransformIdx, usize)>, 62 | outputid: cake::OutputId, 63 | node_editor: &AflakNodeEditor, 64 | ) -> Result<(), Error> 65 | where 66 | S: Data, 67 | F: Fn(f32) -> f32, 68 | { 69 | let p = self.cursor_screen_pos(); 70 | let window_pos = self.window_pos(); 71 | let window_size = self.window_size(); 72 | let size = [window_size[0], window_size[1] - (p[1] - window_pos[1])]; 73 | state.plot( 74 | self, 75 | image, 76 | vtype, 77 | vunit, 78 | axis, 79 | p, 80 | size, 81 | copying, 82 | store, 83 | attaching, 84 | outputid, 85 | node_editor, 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! Every 4 | little bit helps, and credit will always be given. 5 | 6 | ## Bug reports 7 | 8 | When [reporting a bug](https://github.com/aflak-vis/aflak/issues) please 9 | include: 10 | 11 | * Your operating system name and version. 12 | * Your `aflak` version. You can get it by typing `aflak --version` in the 13 | terminal. 14 | * Any details about your local setup that might be helpful in 15 | troubleshooting. 16 | * Detailed steps to reproduce the bug. 17 | 18 | ## Feature requests and feedback 19 | 20 | The best way to send feedback is to file an issue at 21 | https://github.com/aflak-vis/aflak/issues. 22 | 23 | If you are proposing a feature: 24 | 25 | * Explain in detail how it would work. 26 | * Keep the scope as narrow as possible, to make it easier to implement. 27 | * Code contributions are welcome :) 28 | 29 | ## Development 30 | 31 | To set up `aflak` for local development: 32 | 33 | *1.* Fork [aflak](https://github.com/aflak-vis/aflak) 34 | (look for the "Fork" button). 35 | 36 | *2.* Clone your fork locally:: 37 | 38 | ```sh 39 | git clone git@github.com:your_name_here/aflak.git 40 | ``` 41 | 42 | *3.* Create a branch for local development:: 43 | 44 | ```sh 45 | git checkout -b name-of-your-bugfix-or-feature 46 | ``` 47 | 48 | Now you can make your changes locally. 49 | 50 | *4.* When you're done making changes, run all the checks: 51 | 52 | ```sh 53 | cd src 54 | cargo test --all 55 | cargo fmt 56 | ``` 57 | 58 | *5.* Commit your changes and push your branch to GitHub: 59 | 60 | ```sh 61 | git add your-changes 62 | git commit 63 | git push origin name-of-your-bugfix-or-feature 64 | ``` 65 | 66 | *6.* Submit a pull request through the GitHub website. 67 | 68 | ### Pull Request Guidelines 69 | 70 | If you need some code review or feedback while you're developing the code just 71 | make the pull request. 72 | 73 | 74 | # Software structure (for the developer) 75 | 76 | aflak's entry point is in `aflak/src/src/main.rs`. 77 | 78 | This repo has several crates each with a specific and defined objective. 79 | Each of the crates is documented. Please refer to their respective doc using `cargo doc --open`. 80 | 81 | - **aflak_cake** (*Computational mAKE*): Manage node graph and computational tasks (back-end). 82 | 83 | Example for opening **aflak_cake**'s doc: 84 | 85 | ```sh 86 | cd aflak/src/aflak_cake 87 | # Open the doc 88 | cargo doc --open 89 | ``` 90 | 91 | - **aflak_plot**: Visualization library built on *imgui* for *aflak*. 92 | - **aflak_primitives**: Define transformation types and primitives for use in 93 | astrophysics. All built-in nodes are defined in this crate. 94 | Documentation about the list of node can be found 95 | [in the wiki](https://github.com/aflak-vis/aflak/wiki/%E3%83%8E%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7) 96 | (in Japanese): 97 | - **node_editor**: Node editor built on *aflak_cake* and *imgui*. 98 | -------------------------------------------------------------------------------- /src/aflak_cake/tests/support/mod.rs: -------------------------------------------------------------------------------- 1 | pub use aflak_cake::*; 2 | use std::fmt; 3 | use variant_name::VariantName; 4 | 5 | #[derive(Clone, PartialEq, Debug, VariantName, Serialize, Deserialize)] 6 | pub enum AlgoIO { 7 | Integer(u64), 8 | Float(f64), 9 | Image2d(Vec>), 10 | } 11 | 12 | /// `never` type representing an impossible error (similar to ! in rust nightly) 13 | #[derive(Clone, PartialEq, Debug)] 14 | pub enum E {} 15 | 16 | impl fmt::Display for E { 17 | fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { 18 | match *self {} 19 | } 20 | } 21 | 22 | pub fn get_plus1_transform() -> Transform<'static, AlgoIO, E> { 23 | cake_transform!("Add 1", "Calculate", 1, 0, 0, plus1(i: Integer = 0) -> Integer { 24 | vec![Ok(AlgoIO::Integer(i + 1))] 25 | }) 26 | } 27 | 28 | pub fn get_minus1_transform() -> Transform<'static, AlgoIO, E> { 29 | cake_transform!("Substract 1", "Calculate", 1, 0, 0, minus1(i: Integer) -> Integer { 30 | vec![Ok(AlgoIO::Integer(i - 1))] 31 | }) 32 | } 33 | 34 | pub fn get_divide_by_10_transform() -> Transform<'static, AlgoIO, E> { 35 | cake_transform!("Divide by 10", "Calculate", 1, 0, 0, divide_by_10(f: Float) -> Float { 36 | vec![Ok(AlgoIO::Float(f / 10.0))] 37 | }) 38 | } 39 | 40 | pub fn get_get1_transform() -> Transform<'static, AlgoIO, E> { 41 | Transform::new_constant(AlgoIO::Integer(1)) 42 | } 43 | 44 | pub fn get_get_image_transform() -> Transform<'static, AlgoIO, E> { 45 | Transform::new_constant(AlgoIO::Image2d(vec![vec![10.0; 10000]; 10000])) 46 | } 47 | 48 | lazy_static! { 49 | static ref TRANSFORMATIONS: Vec> = { 50 | vec![ 51 | get_plus1_transform(), 52 | get_minus1_transform(), 53 | get_get1_transform(), 54 | get_get_image_transform(), 55 | get_divide_by_10_transform(), 56 | ] 57 | }; 58 | pub static ref TRANSFORMATIONS_REF: &'static [&'static Transform<'static, AlgoIO, E>] = { 59 | let vec = Box::new(TRANSFORMATIONS.iter().collect::>()); 60 | let vec = Box::leak(vec); 61 | vec.as_slice() 62 | }; 63 | } 64 | 65 | impl NamedAlgorithms for AlgoIO { 66 | fn get_transform(s: &str) -> Option<&'static Transform<'static, AlgoIO, E>> { 67 | for t in TRANSFORMATIONS_REF.iter() { 68 | if t.name() == s { 69 | return Some(t); 70 | } 71 | } 72 | None 73 | } 74 | } 75 | 76 | impl ConvertibleVariants for AlgoIO { 77 | const CONVERTION_TABLE: &'static [ConvertibleVariant] = &[ConvertibleVariant { 78 | from: "Integer", 79 | into: "Float", 80 | f: integer_to_float, 81 | }]; 82 | } 83 | 84 | fn integer_to_float(from: &AlgoIO) -> AlgoIO { 85 | if let AlgoIO::Integer(int) = from { 86 | AlgoIO::Float(*int as _) 87 | } else { 88 | panic!("Unexpected input!") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aflak - アフラーク 2 | 3 | **Aflak** - A visualization environment to analyze astronomical datasets 4 | by providing a visual programming language interface. 5 | 6 | [![Build Status](https://travis-ci.com/aflak-vis/aflak.svg?branch=master)](https://travis-ci.com/aflak-vis/aflak) 7 | [![Latest release on crates.io](https://meritbadge.herokuapp.com/aflak)](https://crates.io/crates/aflak) 8 | 9 | ![Screenshot of Aflak](images/aflak-screen.png) 10 | 11 | **IN ACTIVE DEVELOPMENT: Features and API highly unstable!** 12 | 13 | ## Download binary (nightly) 14 | 15 | - [Linux](https://aflak-vis.github.io/download/build/linux/aflak-linux-90d420728.tar.gz) 16 | - [macOS](https://aflak-vis.github.io/download/build/macos/aflak-macos-3dee6276f.tar.gz) 17 | 18 | 19 | ## Getting started 20 | 21 | ```sh 22 | # Open a FITS file with aflak 23 | aflak -f 24 | # See CLI help 25 | aflak --help 26 | ``` 27 | 28 | You may find a demo video [here](https://vimeo.com/290328343). 29 | 30 | **Disclaimer**: Most testing until now has been done with FITS files sampled 31 | from the [SDSS MaNGA](https://www.sdss.org/surveys/manga/), and some files 32 | from ALMA. Standard-compliant FITS should be openable without issue, if 33 | you meet any issue, please file an issue, provide the FITS file, a 34 | screenshot and explain what's wrong/unexpected. 35 | 36 | **NB**: 37 | - The first time you run aflak, the window layout may not be what you 38 | prefer. You may want to resize / move some windows with the mouse the 39 | very first time your run aflak. 40 | Hopefully, aflak remembers the arrangement of your windows between sessions. 41 | - It is advised to use aflak with a large screen more 2000-pixel large 42 | for a better experience. 3000-pixel is even better! 43 | 44 | 45 | ## Build from source 46 | 47 | Install the rust toolchain with [rustup](https://rustup.rs/). 48 | You will need a working C & C++ environment to install from sources. 49 | 50 | ### Quick install (nightly) 51 | 52 | ```sh 53 | cargo install --git https://github.com/aflak-vis/aflak aflak 54 | ``` 55 | 56 | ### Update 57 | 58 | If aflak is already installed, just append the `--force` flag to the `cargo` 59 | command in order to overwrite the current install of aflak with a new one. 60 | 61 | ```sh 62 | cargo install --force --git https://github.com/aflak-vis/aflak aflak 63 | ``` 64 | 65 | ### Slower install 66 | 67 | Clone the git repository. 68 | 69 | ```sh 70 | git clone https://github.com/aflak-vis/aflak 71 | cd aflak/src 72 | cargo install --path . 73 | ``` 74 | 75 | ### Build 76 | 77 | ```sh 78 | cd aflak/src 79 | cargo build --release 80 | ``` 81 | 82 | ## Run aflak from source 83 | 84 | ```sh 85 | cd aflak/src 86 | cargo run --release -- -f 87 | ``` 88 | 89 | ## Contributing, bug report, feature request 90 | 91 | Please read [the contribution guide](./CONTRIBUTING.md). 92 | 93 | ## [Newsletter](https://mailchi.mp/c94954601fe9/aflak-newsletter-subscription) 94 | 95 | Please fill your e-mail address on [this 96 | link](https://mailchi.mp/c94954601fe9/aflak-newsletter-subscription) to 97 | subscribe to aflak's monthly newsletter and follow aflak's development. 98 | -------------------------------------------------------------------------------- /src/aflak_cake/src/dst/node.rs: -------------------------------------------------------------------------------- 1 | use crate::dst::{Output, OutputId, TransformIdx}; 2 | use crate::transform::{Transform, TypeId}; 3 | use variant_name::VariantName; 4 | 5 | /// Identifies a [`Node`] in a [`DST`]. A node can either be a [`Transform`], 6 | /// in that case it is identified by a [`TransformIdx`], or an [`OutputId`]. 7 | /// 8 | /// Use it together with [`DST::get_node`]. 9 | #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] 10 | pub enum NodeId { 11 | Transform(TransformIdx), 12 | Output(OutputId), 13 | } 14 | 15 | /// Represents a [`Node`], which is either a [`Transform`] or some 16 | /// [`Output`]. 17 | pub enum Node<'a, 't: 'a, T: 't, E: 't> { 18 | Transform(&'a Transform<'t, T, E>), 19 | /// [`Output`] is `None` when there is an [`OutputId`] not connected to any 20 | /// [`Output`]. 21 | Output((Option<&'a Output>, String)), 22 | } 23 | 24 | impl<'a, 't, T, E> Node<'a, 't, T, E> 25 | where 26 | T: Clone + VariantName, 27 | { 28 | /// Iterate over each default value 29 | pub fn inputs_default_iter(&self) -> Vec> { 30 | match *self { 31 | Node::Transform(t) => t.defaults(), 32 | Node::Output(_) => vec![None], 33 | } 34 | } 35 | } 36 | 37 | impl<'a, 't, T: VariantName, E> Node<'a, 't, T, E> { 38 | /// Get node's name. 39 | pub fn name(&self, id: &NodeId) -> String { 40 | match (self, id) { 41 | (Node::Transform(t), NodeId::Transform(t_idx)) => { 42 | format!("#{} {}", t_idx.id(), t.name()) 43 | } 44 | (Node::Output((_, name)), NodeId::Output(output_id)) => { 45 | format!("Output #{} {}", output_id.id(), name.clone()) 46 | } 47 | (Node::Transform(_), node_id) => panic!( 48 | "Node and NodeId do not agree on their types. Got a Node::Transform with Id {:?}.", 49 | node_id 50 | ), 51 | (Node::Output(ouput_id), node_id) => panic!( 52 | "Node and NodeId do not agree on their types. Got Node::Output({:?}) with Id {:?}.", 53 | ouput_id, node_id 54 | ), 55 | } 56 | } 57 | 58 | /// Iterate over name of each input slot 59 | pub fn input_slot_names_iter(&self) -> Vec 60 | where 61 | T: Clone, 62 | { 63 | match *self { 64 | Node::Transform(t) => t.inputs().iter().map(|s| s.name_with_type()).collect(), 65 | Node::Output(_) => vec!["Out".to_owned()], 66 | } 67 | } 68 | 69 | /// Return number of inputs 70 | pub fn inputs_count(&self) -> usize { 71 | match *self { 72 | Node::Transform(t) => t.input_types().len(), 73 | Node::Output(_) => 1, 74 | } 75 | } 76 | 77 | /// Iterate over each type of the outputs 78 | pub fn outputs_iter(&self) -> Vec { 79 | match *self { 80 | Node::Transform(t) => t.outputs(), 81 | Node::Output(_) => vec![], 82 | } 83 | } 84 | 85 | /// Return number of outputs 86 | pub fn outputs_count(&self) -> usize { 87 | match *self { 88 | Node::Transform(t) => t.outputs().len(), 89 | Node::Output(_) => 0, 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/src/output_window/visualizable.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use imgui::{ImString, TreeNode, Ui, Window}; 4 | 5 | use crate::cake; 6 | use crate::primitives::fitrs::Fits; 7 | 8 | pub trait Visualizable { 9 | fn visualize(&self, ui: &Ui); 10 | 11 | fn draw<'ui, T>(&self, ui: &'ui Ui, window: Window<'_, T>) 12 | where 13 | T: AsRef, 14 | { 15 | window.build(ui, || self.visualize(ui)); 16 | } 17 | } 18 | 19 | pub struct Initializing; 20 | 21 | impl Visualizable for Initializing { 22 | fn visualize(&self, ui: &Ui) { 23 | ui.text("Initialiazing..."); 24 | } 25 | } 26 | 27 | pub struct Unimplemented { 28 | variant: &'static str, 29 | } 30 | 31 | impl Unimplemented { 32 | pub fn new(t: &T) -> Self { 33 | Unimplemented { 34 | variant: t.variant_name(), 35 | } 36 | } 37 | } 38 | 39 | impl Visualizable for Unimplemented { 40 | fn visualize(&self, ui: &Ui) { 41 | ui.text(format!( 42 | "Cannot visualize variable of type '{}'!", 43 | self.variant 44 | )); 45 | } 46 | } 47 | 48 | impl Visualizable for cake::compute::ComputeError { 49 | fn visualize(&self, ui: &Ui) { 50 | ui.text_wrapped(&ImString::new(format!("{}", self))); 51 | } 52 | } 53 | 54 | impl Visualizable for Fits { 55 | fn visualize(&self, ui: &Ui) { 56 | let mut has_hdus = false; 57 | for (i, hdu) in self.iter().enumerate() { 58 | use crate::primitives::fitrs::HeaderValue::*; 59 | use std::borrow::Cow; 60 | 61 | has_hdus = true; 62 | let extname; 63 | let tree_name = match hdu.value("EXTNAME") { 64 | Some(CharacterString(extname)) => extname, 65 | _ => { 66 | if i == 0 { 67 | extname = format!("Primary HDU"); 68 | } else { 69 | extname = format!("Hdu #{}", i); 70 | } 71 | &extname 72 | } 73 | }; 74 | 75 | let id_stack = ui.push_id(i as i32); 76 | TreeNode::new(&tree_name).build(&ui, || { 77 | for (key, value) in &hdu { 78 | ui.text(key); 79 | if let Some(value) = value { 80 | ui.same_line(/*150.0*/); 81 | let value = match value { 82 | CharacterString(s) => Cow::Borrowed(s.as_str()), 83 | Logical(true) => Cow::Borrowed("True"), 84 | Logical(false) => Cow::Borrowed("False"), 85 | IntegerNumber(i) => Cow::Owned(format!("{}", i)), 86 | RealFloatingNumber(f) => Cow::Owned(format!("{:E}", f)), 87 | ComplexIntegerNumber(a, b) => Cow::Owned(format!("{} + {}i", a, b)), 88 | ComplexFloatingNumber(a, b) => { 89 | Cow::Owned(format!("{:E} + {:E}i", a, b)) 90 | } 91 | }; 92 | ui.text(value); 93 | } 94 | ui.separator(); 95 | } 96 | }); 97 | id_stack.pop(); 98 | } 99 | if !has_hdus { 100 | ui.text("Input Fits appears invalid. No HDU could be found."); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/aflak_primitives/src/fits.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | 4 | use fitrs::FitsDataArray; 5 | use ndarray::{self, ArrayD, IxDyn}; 6 | 7 | pub trait FitsDataToArray { 8 | type Target; 9 | 10 | fn to_array(self) -> Result; 11 | } 12 | 13 | #[derive(Debug)] 14 | pub enum FitsArrayReadError { 15 | UnexpectedDimension { expected: usize, got: usize }, 16 | ShapeError(ndarray::ShapeError), 17 | UnsupportedData(&'static str), 18 | } 19 | 20 | impl fmt::Display for FitsArrayReadError { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | match *self { 23 | FitsArrayReadError::UnexpectedDimension { expected, got } => write!( 24 | f, 25 | "Expects a {}-dimensional FITS file. But the input file has {} dimensions.", 26 | expected, got 27 | ), 28 | FitsArrayReadError::ShapeError(ref e) => e.fmt(f), 29 | FitsArrayReadError::UnsupportedData(format) => { 30 | write!(f, "Unsupported data array format: '{}'.", format) 31 | } 32 | } 33 | } 34 | } 35 | 36 | impl error::Error for FitsArrayReadError { 37 | fn description(&self) -> &'static str { 38 | "FitsArrayReadError" 39 | } 40 | } 41 | 42 | impl FitsDataToArray for FitsDataArray { 43 | type Target = ArrayD; 44 | 45 | fn to_array(self) -> Result, FitsArrayReadError> { 46 | let sh: Vec<_> = self.shape.into_iter().rev().collect(); 47 | ArrayD::from_shape_vec(sh, self.data).map_err(FitsArrayReadError::ShapeError) 48 | } 49 | } 50 | 51 | impl FitsDataToArray for FitsDataArray { 52 | type Target = ArrayD; 53 | 54 | fn to_array(self) -> Result, FitsArrayReadError> { 55 | let sh: Vec<_> = self.shape.into_iter().rev().collect(); 56 | ArrayD::from_shape_vec(sh, self.data.into_iter().map(|f| f as f32).collect()) 57 | .map_err(FitsArrayReadError::ShapeError) 58 | } 59 | } 60 | 61 | impl FitsDataToArray for FitsDataArray> { 62 | type Target = ArrayD; 63 | 64 | fn to_array(self) -> Result, FitsArrayReadError> { 65 | let sh: Vec<_> = self.shape.into_iter().rev().collect(); 66 | ArrayD::from_shape_vec( 67 | sh, 68 | self.data 69 | .into_iter() 70 | .map(|some_int| { 71 | if let Some(int) = some_int { 72 | int as f32 73 | } else { 74 | ::std::f32::NAN 75 | } 76 | }) 77 | .collect(), 78 | ) 79 | .map_err(FitsArrayReadError::ShapeError) 80 | } 81 | } 82 | 83 | impl FitsDataToArray for FitsDataArray> { 84 | type Target = ArrayD; 85 | 86 | fn to_array(self) -> Result, FitsArrayReadError> { 87 | let sh: Vec<_> = self.shape.into_iter().rev().collect(); 88 | ArrayD::from_shape_vec( 89 | sh, 90 | self.data 91 | .into_iter() 92 | .map(|some_int| { 93 | if let Some(int) = some_int { 94 | int as f32 95 | } else { 96 | ::std::f32::NAN 97 | } 98 | }) 99 | .collect(), 100 | ) 101 | .map_err(FitsArrayReadError::ShapeError) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/src/output_window/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::error; 3 | 4 | mod menu_bar; 5 | mod visualizable; 6 | 7 | use glium; 8 | use imgui::{Ui, Window}; 9 | use owning_ref::ArcRef; 10 | 11 | use crate::aflak_plot::{ 12 | imshow::{self, Textures}, 13 | plot, scatter_lineplot, InteractionId, 14 | }; 15 | use crate::cake::{OutputId, TransformIdx}; 16 | use crate::primitives::{ndarray, IOValue, SuccessOut}; 17 | use implot::Context; 18 | 19 | use self::menu_bar::MenuBar; 20 | use self::visualizable::{Initializing, Unimplemented, Visualizable}; 21 | use crate::aflak::AflakNodeEditor; 22 | 23 | #[derive(Default)] 24 | pub struct OutputWindow { 25 | image1d_state: plot::State, 26 | image2d_state: imshow::State>>, 27 | scatter_lineplot_state: scatter_lineplot::State, 28 | pub editable_values: EditableValues, 29 | show_pixels: bool, 30 | } 31 | 32 | type EditableValues = HashMap; 33 | 34 | impl OutputWindow { 35 | pub fn draw<'ui, F, T>( 36 | &mut self, 37 | ui: &'ui Ui, 38 | output: OutputId, 39 | window: Window<'_, T>, 40 | node_editor: &mut AflakNodeEditor, 41 | gl_ctx: &F, 42 | textures: &mut Textures, 43 | plotcontext: &Context, 44 | copying: &mut Option<(InteractionId, TransformIdx)>, 45 | attaching: &mut Option<(OutputId, TransformIdx, usize)>, 46 | ) -> Vec> 47 | where 48 | F: glium::backend::Facade, 49 | T: AsRef, 50 | { 51 | let compute_state = node_editor.compute_output(output); 52 | match compute_state { 53 | None => { 54 | Initializing.draw(ui, window); 55 | vec![] 56 | } 57 | Some(Err(e)) => { 58 | e.draw(ui, window); 59 | vec![] 60 | } 61 | Some(Ok(result)) => { 62 | let created_on = SuccessOut::created_on(&result); 63 | let value = SuccessOut::take(result); 64 | let ctx = menu_bar::OutputWindowCtx { 65 | ui, 66 | output, 67 | value: &value, 68 | window: self, 69 | created_on, 70 | node_editor, 71 | gl_ctx, 72 | textures, 73 | plotcontext, 74 | copying, 75 | attaching, 76 | }; 77 | match &*value { 78 | IOValue::Str(ref string) => string.draw(ctx, window), 79 | IOValue::Paths(ref files) => files.draw(ctx, window), 80 | IOValue::Integer(integer) => integer.draw(ctx, window), 81 | IOValue::Float(float) => float.draw(ctx, window), 82 | IOValue::Float2(floats) => floats.draw(ctx, window), 83 | IOValue::Float3(floats) => floats.draw(ctx, window), 84 | IOValue::Float3x3(floats) => floats.draw(ctx, window), 85 | IOValue::Bool(b) => b.draw(ctx, window), 86 | IOValue::Image(ref image) => image.draw(ctx, window), 87 | IOValue::Roi(ref roi) => roi.draw(ctx, window), 88 | IOValue::Fits(ref fits) => { 89 | fits.draw(ui, window); 90 | vec![] 91 | } 92 | val => { 93 | Unimplemented::new(val).draw(ui, window); 94 | vec![] 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/aflak_plot/src/lims.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use ndarray::{self, ArrayBase}; 4 | 5 | use super::Error; 6 | 7 | pub fn get_vmin(image: &ArrayBase) -> Result 8 | where 9 | S: ndarray::Data, 10 | D: ndarray::Dimension, 11 | { 12 | let min = image 13 | .iter() 14 | .min_by(|&&f1, &&f2| float_compare_nan_max(f1, f2)); 15 | if let Some(min) = min { 16 | Ok(*min) 17 | } else { 18 | Err(Error::Msg("Could not get vmin")) 19 | } 20 | } 21 | 22 | pub fn get_vmax(image: &ArrayBase) -> Result 23 | where 24 | S: ndarray::Data, 25 | D: ndarray::Dimension, 26 | { 27 | let max = image 28 | .iter() 29 | .max_by(|&&f1, &&f2| float_compare_nan_min(f1, f2)); 30 | if let Some(max) = max { 31 | Ok(*max) 32 | } else { 33 | Err(Error::Msg("Could not get vmax")) 34 | } 35 | } 36 | 37 | pub fn get_vmed_normalized(image: &ArrayBase) -> Result 38 | where 39 | S: ndarray::Data, 40 | D: ndarray::Dimension, 41 | { 42 | let mut data = Vec::new(); 43 | let vmax = get_vmax(image); 44 | let vmin = get_vmin(image); 45 | if let (Ok(vmax), Ok(vmin)) = (vmax, vmin) { 46 | for i in image.iter() { 47 | let d = (i - vmin) / (vmax - vmin); 48 | if !d.is_nan() && !d.is_infinite() { 49 | data.push(d); 50 | } 51 | } 52 | data.sort_by(|a, b| a.partial_cmp(b).unwrap()); 53 | if data.len() == 0 { 54 | Err(Error::Msg("Empty data!, med")) 55 | } else { 56 | let mid = data.len() / 2; 57 | let med = if data.len() % 2 == 0 { 58 | (data[mid] + data[mid - 1]) / 2.0 59 | } else { 60 | data[mid] 61 | }; 62 | data.clear(); 63 | Ok(med) 64 | } 65 | } else { 66 | Err(Error::Msg("Could not get vmed from normalized image")) 67 | } 68 | } 69 | 70 | pub fn get_vmad_normalized(image: &ArrayBase, median: f32) -> Result 71 | where 72 | S: ndarray::Data, 73 | D: ndarray::Dimension, 74 | { 75 | let mut data = Vec::new(); 76 | let vmax = get_vmax(image); 77 | let vmin = get_vmin(image); 78 | let vmed = if median.is_nan() { 79 | get_vmed_normalized(image) 80 | } else { 81 | Ok(median) 82 | }; 83 | if let (Ok(vmax), Ok(vmin), Ok(vmed)) = (vmax, vmin, vmed) { 84 | for i in image.iter() { 85 | let d = ((i - vmin) / (vmax - vmin) - vmed).abs(); 86 | if !d.is_nan() && !d.is_infinite() { 87 | data.push(d); 88 | } 89 | } 90 | data.sort_by(|a, b| a.partial_cmp(b).unwrap()); 91 | if data.len() == 0 { 92 | Err(Error::Msg("Empty data!, mad")) 93 | } else { 94 | let mid = data.len() / 2; 95 | let med = if data.len() % 2 == 0 { 96 | (data[mid] + data[mid - 1]) / 2.0 97 | } else { 98 | data[mid] 99 | }; 100 | data.clear(); 101 | Ok(med) 102 | } 103 | } else { 104 | Err(Error::Msg("Could not get vmad from normalized image")) 105 | } 106 | } 107 | 108 | fn float_compare_nan_min(f1: f32, f2: f32) -> Ordering { 109 | PartialOrd::partial_cmp(&f1, &f2).unwrap_or_else(|| match (f32::is_nan(f1), f32::is_nan(f2)) { 110 | (true, true) => Ordering::Equal, 111 | (true, false) => Ordering::Less, 112 | (false, true) => Ordering::Greater, 113 | _ => unreachable!(), 114 | }) 115 | } 116 | 117 | fn float_compare_nan_max(f1: f32, f2: f32) -> Ordering { 118 | PartialOrd::partial_cmp(&f1, &f2).unwrap_or_else(|| match (f32::is_nan(f1), f32::is_nan(f2)) { 119 | (true, true) => Ordering::Equal, 120 | (true, false) => Ordering::Greater, 121 | (false, true) => Ordering::Less, 122 | _ => unreachable!(), 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /src/src/file_dialog.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::path::PathBuf; 3 | 4 | use imgui::{ChildWindow, Condition, ImString, Ui, Window}; 5 | use imgui_file_explorer::UiFileExplorer; 6 | 7 | use crate::aflak::AflakNodeEditor; 8 | use crate::templates; 9 | 10 | pub struct FileDialog { 11 | selected_template: usize, 12 | file_selection: FileSelection, 13 | } 14 | 15 | enum FileSelection { 16 | FileNotSelected, 17 | FileSelected { path: PathBuf }, 18 | } 19 | 20 | pub enum FileDialogEvent { 21 | Close, 22 | Selection(FileDialogResult), 23 | } 24 | 25 | pub struct FileDialogResult { 26 | pub path: PathBuf, 27 | template: String, 28 | } 29 | 30 | impl Default for FileDialog { 31 | fn default() -> Self { 32 | Self { 33 | selected_template: 0, 34 | file_selection: FileSelection::FileNotSelected, 35 | } 36 | } 37 | } 38 | 39 | impl FileDialog { 40 | pub fn with_path(path: PathBuf) -> Self { 41 | Self { 42 | selected_template: 0, 43 | file_selection: FileSelection::FileSelected { path }, 44 | } 45 | } 46 | 47 | pub fn build(&mut self, ui: &Ui) -> Option { 48 | let selected_template = &mut self.selected_template; 49 | let mut some_path = None; 50 | let mut opened = true; 51 | let template_names = [ 52 | format!("waveform"), 53 | format!("equivalent_width"), 54 | format!("fits_cleaning"), 55 | format!("velocity_field"), 56 | ]; 57 | match &self.file_selection { 58 | FileSelection::FileNotSelected => { 59 | Window::new(format!("Open file")) 60 | .focus_on_appearing(true) 61 | .opened(&mut opened) 62 | .build(ui, || { 63 | ui.combo_simple_string( 64 | format!("Template"), 65 | selected_template, 66 | &template_names, 67 | ); 68 | ChildWindow::new("file-explorer") 69 | .size([0.0, 512.0]) 70 | .build(ui, || { 71 | if let Ok((path, _)) = ui.file_explorer("/", &["fits"]) { 72 | some_path = path; 73 | } 74 | }) 75 | }); 76 | } 77 | FileSelection::FileSelected { path } => { 78 | Window::new(&ImString::new(format!("Open {:?}", path))) 79 | .focus_on_appearing(true) 80 | .opened(&mut opened) 81 | .size([512.0, 0.0], Condition::FirstUseEver) 82 | .build(ui, || { 83 | ui.combo_simple_string( 84 | format!("Template"), 85 | selected_template, 86 | &template_names, 87 | ); 88 | if ui.button(format!("OK")) { 89 | some_path = Some(path.clone()); 90 | } 91 | }); 92 | } 93 | } 94 | if !opened { 95 | Some(FileDialogEvent::Close) 96 | } else if let Some(path) = some_path { 97 | Some(FileDialogEvent::Selection(FileDialogResult { 98 | path, 99 | template: format!("{}", template_names[*selected_template]), 100 | })) 101 | } else { 102 | None 103 | } 104 | } 105 | } 106 | 107 | impl FileDialogResult { 108 | pub fn to_node_editor(&self) -> Result { 109 | if let Some(import_data) = templates::select_template(&self.template, &self.path) { 110 | AflakNodeEditor::from_export_buf(import_data) 111 | } else { 112 | unreachable!("Got '{}', an unexpected result.", self.template) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/aflak_primitives/src/precond.rs: -------------------------------------------------------------------------------- 1 | /// Try to convert an integer into a usize. 2 | /// Return a `Result`. 3 | macro_rules! try_into_unsigned { 4 | ($value: ident) => { 5 | if $value >= 0 { 6 | Ok($value as usize) 7 | } else { 8 | Err($crate::IOErr::UnexpectedInput(format!( 9 | "'{}' must be positive, but got {}", 10 | stringify!($value), 11 | $value 12 | ))) 13 | } 14 | }; 15 | } 16 | 17 | /// Check that a custom condition is fulfilled. 18 | macro_rules! precheck { 19 | ($start: ident $op :tt $end: ident) => { 20 | if $start $op $end { 21 | Ok(()) 22 | } else { 23 | Err($crate::IOErr::UnexpectedInput(format!( 24 | "Expected {} {} {}, but got {} {} {}", 25 | stringify!($start), 26 | stringify!($op), 27 | stringify!($end), 28 | $start, 29 | stringify!($op), 30 | $end, 31 | ))) 32 | } 33 | }; 34 | ($cond: expr, $($arg:tt)*) => { 35 | if $cond { 36 | Ok(()) 37 | } else { 38 | Err($crate::IOErr::UnexpectedInput(format!($($arg)*))) 39 | } 40 | }; 41 | } 42 | 43 | /// Check that a WcsArray has more than 0 dimensions. 44 | /// If so, return the number of frames along the first dimension. 45 | macro_rules! has_gt_0_dim { 46 | ($wcs_array: ident) => { 47 | if let Some(frame_cnt) = $wcs_array.scalar().dim().as_array_view().first() { 48 | Ok(*frame_cnt) 49 | } else { 50 | Err($crate::IOErr::UnexpectedInput(format!( 51 | "'{}' is a 0-dimensional image, cannot slice", 52 | stringify!($wcs_array) 53 | ))) 54 | } 55 | }; 56 | } 57 | 58 | /// Check that a WcsArray has more than $dim dimensions. 59 | macro_rules! dim_is { 60 | ($wcs_array: ident, $dim: expr) => {{ 61 | let expected_dim = $dim; 62 | let got_dim = $wcs_array.scalar().ndim(); 63 | if got_dim == expected_dim { 64 | Ok(()) 65 | } else { 66 | Err($crate::IOErr::UnexpectedInput(format!( 67 | "'{}' is a {}-dimensional image, while dimension {} was expected", 68 | stringify!($wcs_array), 69 | got_dim, 70 | expected_dim 71 | ))) 72 | } 73 | }}; 74 | } 75 | 76 | /// Check that a WcsArray is sliceable from indices 'start' to 'end'. 77 | macro_rules! is_sliceable { 78 | ($wcs_array: ident, $frame_idx: ident) => { 79 | has_gt_0_dim!($wcs_array).and_then(|frame_cnt| { 80 | precheck!( 81 | $frame_idx < frame_cnt, 82 | "'{}' greater or equal to the input image '{}''s frame count (expected {} < {})", 83 | stringify!($frame_idx), stringify!($wcs_array), $frame_idx, frame_cnt 84 | ) 85 | }) 86 | }; 87 | ($wcs_array: ident, $start: ident, $end: ident) => { 88 | has_gt_0_dim!($wcs_array).and_then(|frame_cnt| { 89 | precheck!($start < $end).and_then(|_| { 90 | precheck!( 91 | $end < frame_cnt + 1, 92 | "'{}' greater or equal to the input image '{}''s frame count (expected {} < {})", 93 | stringify!($end), stringify!($wcs_array), $end, frame_cnt + 1 94 | ) 95 | }) 96 | }) 97 | }; 98 | } 99 | 100 | /// Check that two WcsArray have the same dimensions 101 | macro_rules! are_same_dim { 102 | ($wcs_array1: ident, $wcs_array2: ident) => {{ 103 | let i1_dim = $wcs_array1.scalar().dim(); 104 | let i2_dim = $wcs_array2.scalar().dim(); 105 | if i1_dim == i2_dim { 106 | Ok(()) 107 | } else { 108 | Err(IOErr::UnexpectedInput(format!( 109 | "Cannot compose images of different dimensions ('{}' has dimension {:?}, while '{}' has dimension {:?})", 110 | stringify!($wcs_array1), i1_dim, stringify!($wcs_array2), i2_dim, 111 | ))) 112 | } 113 | }}; 114 | } 115 | -------------------------------------------------------------------------------- /src/node_editor/src/node_state.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{btree_map, BTreeMap}; 2 | 3 | use crate::cake; 4 | 5 | use crate::vec2::Vec2; 6 | 7 | #[derive(Clone, Debug, Serialize, Deserialize)] 8 | pub struct NodeState { 9 | #[serde(skip_serializing, skip_deserializing)] 10 | pub selected: bool, 11 | pub pos: Vec2, 12 | pub size: Vec2, 13 | } 14 | 15 | impl Default for NodeState { 16 | fn default() -> Self { 17 | Self { 18 | selected: false, 19 | pos: Vec2::default(), 20 | size: Vec2::default(), 21 | } 22 | } 23 | } 24 | 25 | impl NodeState { 26 | pub fn get_pos(&self, font_window_scale: f32) -> Vec2 { 27 | self.pos * font_window_scale 28 | } 29 | 30 | pub fn get_input_slot_pos, C: Into>( 31 | &self, 32 | slot_idx: I, 33 | slot_cnt: C, 34 | font_window_scale: f32, 35 | ) -> Vec2 { 36 | Vec2::new(( 37 | self.pos.0 * font_window_scale, 38 | self.pos.1 * font_window_scale 39 | + self.size.1 * (slot_idx.into() + 1) as f32 / (slot_cnt.into() + 1) as f32, 40 | )) 41 | } 42 | 43 | pub fn get_output_slot_pos, C: Into>( 44 | &self, 45 | slot_idx: I, 46 | slot_cnt: C, 47 | font_window_scale: f32, 48 | ) -> Vec2 { 49 | Vec2::new(( 50 | self.pos.0 * font_window_scale + self.size.0, 51 | self.pos.1 * font_window_scale 52 | + self.size.1 * (slot_idx.into() + 1) as f32 / (slot_cnt.into() + 1) as f32, 53 | )) 54 | } 55 | } 56 | 57 | #[derive(Clone, Debug)] 58 | pub struct NodeStates(BTreeMap); 59 | 60 | impl NodeStates { 61 | pub fn get(&self, id: &cake::NodeId) -> Option<&NodeState> { 62 | self.0.get(id) 63 | } 64 | 65 | pub fn iter(&self) -> btree_map::Iter { 66 | self.0.iter() 67 | } 68 | 69 | /// Insert/Replace state for specific [`cake::NodeId`]. 70 | /// Only used to reconstruct [`NodeStates`] during import. 71 | pub fn insert(&mut self, id: cake::NodeId, state: NodeState) { 72 | self.0.insert(id, state); 73 | } 74 | 75 | pub fn new() -> Self { 76 | NodeStates(BTreeMap::new()) 77 | } 78 | 79 | pub fn init_node(&mut self, id: &cake::NodeId, clue: Vec2) { 80 | if !self.0.contains_key(id) { 81 | let new_node = init_node(self, clue); 82 | self.0.insert(*id, new_node); 83 | } 84 | } 85 | 86 | pub fn deselect_all(&mut self) { 87 | for state in self.0.values_mut() { 88 | state.selected = false; 89 | } 90 | } 91 | 92 | pub fn toggle_select(&mut self, id: &cake::NodeId) { 93 | let state = self.0.get_mut(id).unwrap(); 94 | state.selected = !state.selected; 95 | } 96 | 97 | pub fn get_state(&self, id: &cake::NodeId, f: F) -> T 98 | where 99 | F: FnOnce(&NodeState) -> T, 100 | { 101 | let state = &self.0[id]; 102 | f(state) 103 | } 104 | 105 | pub fn set_state(&mut self, id: &cake::NodeId, f: F) -> T 106 | where 107 | F: FnOnce(&mut NodeState) -> T, 108 | { 109 | let state = self.0.get_mut(id).unwrap(); 110 | f(state) 111 | } 112 | 113 | pub fn remove_node(&mut self, id: &cake::NodeId) -> Option { 114 | self.0.remove(id) 115 | } 116 | } 117 | 118 | fn init_node(node_states: &NodeStates, clue: Vec2) -> NodeState { 119 | let mut pos = clue; 120 | // Run very simple heuristic to prevent new nodes from appearing on top of a 121 | // previous node 122 | for state in node_states.0.values() { 123 | if pos.0 >= state.pos.0 124 | && pos.0 <= state.pos.0 + state.size.0 125 | && pos.1 >= state.pos.1 126 | && pos.1 <= state.pos.1 + state.size.1 127 | { 128 | // pos is over another node, so move it aside (on the right) 129 | const MIN_MARGIN_BETWEEN_NODES: f32 = 10.0; 130 | pos.0 = state.pos.0 + state.size.0 + MIN_MARGIN_BETWEEN_NODES; 131 | } 132 | } 133 | NodeState { 134 | pos, 135 | ..Default::default() 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/aflak_plot/examples/scatter.rs: -------------------------------------------------------------------------------- 1 | extern crate aflak_imgui_glium_support as support; 2 | extern crate aflak_plot; 3 | extern crate glium; 4 | extern crate imgui; 5 | extern crate implot; 6 | #[macro_use(s)] 7 | extern crate ndarray; 8 | 9 | use aflak_plot::imshow::cake::OutputId; 10 | use std::collections::HashMap; 11 | use std::path::PathBuf; 12 | 13 | use aflak_plot::{ 14 | scatter_lineplot::{State, UiScatter}, 15 | AxisTransform, 16 | }; 17 | use imgui::{Condition, Ui, Window}; 18 | use implot::{ 19 | push_style_var_f32, push_style_var_i32, Context, Marker, Plot, PlotScatter, PlotUi, StyleVar, 20 | }; 21 | 22 | fn main() { 23 | let config = support::AppConfig { 24 | title: "Example".to_owned(), 25 | ini_filename: Some(PathBuf::from("scatter.ini")), 26 | ..Default::default() 27 | }; 28 | let mut state = State::default(); 29 | let plotcontext = Context::create(); 30 | 31 | support::init(config).main_loop(move |ui, _, _| { 32 | let plot_ui = plotcontext.get_plot_ui(); 33 | let image_data = { 34 | const WIDTH: usize = 10; 35 | const HEIGHT: usize = 10; 36 | let mut image_data = Vec::with_capacity(WIDTH * HEIGHT * 3); 37 | for c in 0..3 { 38 | for j in 0..WIDTH { 39 | for i in 0..HEIGHT { 40 | if c == 0 { 41 | image_data.push(j as f32 * 0.1); 42 | } else if c == 1 { 43 | image_data.push(i as f32 * 0.1); 44 | } else if c == 2 { 45 | image_data.push(if i > 5 { 1.0 } else { 2.0 }); 46 | } 47 | } 48 | } 49 | } 50 | ndarray::Array::from_shape_vec(vec![3, WIDTH * HEIGHT], image_data).unwrap() 51 | }; 52 | let image_data = image_data.slice(s![.., ..]); 53 | Window::new(format!("Scatter plots example")) 54 | .size([430.0, 450.0], Condition::FirstUseEver) 55 | .build(ui, || { 56 | ui.scatter( 57 | &image_data, 58 | &plot_ui, 59 | Some(&AxisTransform::new("X Axis", "m", |x| x)), 60 | Some(&AxisTransform::new("Y Axis", "m", |y| y)), 61 | &mut state, 62 | &mut None, 63 | &mut HashMap::new(), 64 | &mut None, 65 | OutputId::new(0), 66 | ) 67 | .expect("Scatter failed"); 68 | }); 69 | true 70 | }); 71 | } 72 | 73 | pub fn show_custom_markers_plot(ui: &Ui, plot_ui: &PlotUi) { 74 | ui.text(format!( 75 | "This header shows how markers can be used in scatter plots." 76 | )); 77 | let content_width = ui.window_content_region_width(); 78 | Plot::new("Multi-marker scatter plot") 79 | // The size call could also be omitted, though the defaults don't consider window 80 | // width, which is why we're not doing so here. 81 | .size([content_width, 300.0]) 82 | .build(plot_ui, || { 83 | // Change to cross marker for one scatter plot call 84 | let x_positions = vec![0.1, 0.2, 0.1, 0.5, 0.9]; 85 | let y_positions = vec![0.1, 0.1, 0.3, 0.3, 0.9]; 86 | let markerchoice = push_style_var_i32(&StyleVar::Marker, Marker::Cross as i32); 87 | PlotScatter::new("legend label 1").plot(&x_positions, &y_positions); 88 | markerchoice.pop(); 89 | 90 | // One can combine things like marker size and markor choice 91 | let x_positions = vec![0.4, 0.1]; 92 | let y_positions = vec![0.5, 0.3]; 93 | let marker_choice = push_style_var_i32(&StyleVar::Marker, Marker::Diamond as i32); 94 | let marker_size = push_style_var_f32(&StyleVar::MarkerSize, 12.0); 95 | PlotScatter::new("legend label 2").plot(&x_positions, &y_positions); 96 | 97 | // TODO(4bb4) check if these have to be in reverse push order. Does not 98 | // seem to be the case. 99 | marker_size.pop(); 100 | marker_choice.pop(); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /src/imgui_tone_curve/examples/support/mod.rs: -------------------------------------------------------------------------------- 1 | use glium::backend::{self, Facade}; 2 | use glium::glutin; 3 | use glium::glutin::event::{Event, WindowEvent}; 4 | use glium::glutin::event_loop::ControlFlow; 5 | use glium::{Display, Surface}; 6 | use imgui::{Context, FontConfig, FontSource, Textures, Ui}; 7 | use imgui_glium_renderer::{Renderer, Texture}; 8 | use imgui_winit_support::{HiDpiMode, WinitPlatform}; 9 | use std::rc::Rc; 10 | use std::time::Instant; 11 | 12 | pub struct System { 13 | pub events_loop: glutin::event_loop::EventLoop<()>, 14 | pub display: glium::Display, 15 | pub imgui: Context, 16 | pub platform: WinitPlatform, 17 | pub renderer: Renderer, 18 | pub font_size: f32, 19 | } 20 | 21 | pub fn init(title: &str) -> System { 22 | let title = match title.rfind('/') { 23 | Some(idx) => title.split_at(idx + 1).1, 24 | None => title, 25 | }; 26 | let events_loop = glutin::event_loop::EventLoop::new(); 27 | let context = glutin::ContextBuilder::new().with_vsync(true); 28 | let builder = glutin::window::WindowBuilder::new() 29 | .with_title(title.to_owned()) 30 | .with_inner_size(glutin::dpi::LogicalSize::new(1024f64, 768f64)); 31 | let display = 32 | Display::new(builder, context, &events_loop).expect("Failed to initialize display"); 33 | 34 | let mut imgui = Context::create(); 35 | imgui.set_ini_filename(None); 36 | 37 | let mut platform = WinitPlatform::init(&mut imgui); 38 | { 39 | let gl_window = display.gl_window(); 40 | let window = gl_window.window(); 41 | platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded); 42 | } 43 | 44 | let hidpi_factor = platform.hidpi_factor(); 45 | let font_size = (13.0 * hidpi_factor) as f32; 46 | imgui.fonts().add_font(&[FontSource::DefaultFontData { 47 | config: Some(FontConfig { 48 | size_pixels: font_size, 49 | ..FontConfig::default() 50 | }), 51 | }]); 52 | 53 | imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; 54 | 55 | let renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer"); 56 | 57 | System { 58 | events_loop, 59 | display, 60 | imgui, 61 | platform, 62 | renderer, 63 | font_size, 64 | } 65 | } 66 | 67 | impl System { 68 | pub fn main_loop< 69 | F: FnMut(&mut bool, &mut Ui, &Rc, &mut Textures) -> bool + 'static, 70 | >( 71 | self, 72 | mut run_ui: F, 73 | ) { 74 | let System { 75 | events_loop, 76 | display, 77 | mut imgui, 78 | mut platform, 79 | mut renderer, 80 | .. 81 | } = self; 82 | let mut last_frame = Instant::now(); 83 | events_loop.run(move |event, _, control_flow| match event { 84 | Event::NewEvents(_) => { 85 | let now = Instant::now(); 86 | imgui.io_mut().update_delta_time(now - last_frame); 87 | last_frame = now; 88 | } 89 | Event::MainEventsCleared => { 90 | let gl_window = display.gl_window(); 91 | platform 92 | .prepare_frame(imgui.io_mut(), &gl_window.window()) 93 | .expect("Failed to start frame"); 94 | gl_window.window().request_redraw(); 95 | } 96 | Event::RedrawRequested(_) => { 97 | let mut ui = imgui.frame(); 98 | 99 | let mut run = true; 100 | run = run_ui( 101 | &mut run, 102 | &mut ui, 103 | display.get_context(), 104 | renderer.textures(), 105 | ); 106 | if !run { 107 | *control_flow = ControlFlow::Exit; 108 | } 109 | 110 | let gl_window = display.gl_window(); 111 | let mut target = display.draw(); 112 | target.clear_color(1.0, 1.0, 1.0, 1.0); 113 | platform.prepare_render(&ui, gl_window.window()); 114 | let draw_data = ui.render(); 115 | renderer 116 | .render(&mut target, draw_data) 117 | .expect("Rendering failed"); 118 | target.finish().expect("Failed to swap buffers"); 119 | } 120 | Event::WindowEvent { 121 | event: WindowEvent::CloseRequested, 122 | .. 123 | } => *control_flow = ControlFlow::Exit, 124 | event => { 125 | let gl_window = display.gl_window(); 126 | platform.handle_event(imgui.io_mut(), gl_window.window(), &event); 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/imgui_file_explorer/examples/support/mod.rs: -------------------------------------------------------------------------------- 1 | use glium::backend::{self, Facade}; 2 | use glium::glutin; 3 | use glium::glutin::event::{Event, WindowEvent}; 4 | use glium::glutin::event_loop::ControlFlow; 5 | use glium::{Display, Surface}; 6 | use imgui::{Context, FontConfig, FontSource, Textures, Ui}; 7 | use imgui_glium_renderer::{Renderer, Texture}; 8 | use imgui_winit_support::{HiDpiMode, WinitPlatform}; 9 | use std::rc::Rc; 10 | use std::time::Instant; 11 | 12 | pub struct System { 13 | pub events_loop: glutin::event_loop::EventLoop<()>, 14 | pub display: glium::Display, 15 | pub imgui: Context, 16 | pub platform: WinitPlatform, 17 | pub renderer: Renderer, 18 | pub font_size: f32, 19 | } 20 | 21 | pub fn init(title: &str) -> System { 22 | let title = match title.rfind('/') { 23 | Some(idx) => title.split_at(idx + 1).1, 24 | None => title, 25 | }; 26 | let events_loop = glutin::event_loop::EventLoop::new(); 27 | let context = glutin::ContextBuilder::new().with_vsync(true); 28 | let builder = glutin::window::WindowBuilder::new() 29 | .with_title(title.to_owned()) 30 | .with_inner_size(glutin::dpi::LogicalSize::new(1024f64, 768f64)); 31 | let display = 32 | Display::new(builder, context, &events_loop).expect("Failed to initialize display"); 33 | 34 | let mut imgui = Context::create(); 35 | imgui.set_ini_filename(None); 36 | 37 | let mut platform = WinitPlatform::init(&mut imgui); 38 | { 39 | let gl_window = display.gl_window(); 40 | let window = gl_window.window(); 41 | platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded); 42 | } 43 | 44 | let hidpi_factor = platform.hidpi_factor(); 45 | let font_size = (13.0 * hidpi_factor) as f32; 46 | imgui.fonts().add_font(&[FontSource::DefaultFontData { 47 | config: Some(FontConfig { 48 | size_pixels: font_size, 49 | ..FontConfig::default() 50 | }), 51 | }]); 52 | 53 | imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; 54 | 55 | let renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer"); 56 | 57 | System { 58 | events_loop, 59 | display, 60 | imgui, 61 | platform, 62 | renderer, 63 | font_size, 64 | } 65 | } 66 | 67 | impl System { 68 | pub fn main_loop< 69 | F: FnMut(&mut bool, &mut Ui, &Rc, &mut Textures) -> bool + 'static, 70 | >( 71 | self, 72 | mut run_ui: F, 73 | ) { 74 | let System { 75 | events_loop, 76 | display, 77 | mut imgui, 78 | mut platform, 79 | mut renderer, 80 | .. 81 | } = self; 82 | let mut last_frame = Instant::now(); 83 | events_loop.run(move |event, _, control_flow| match event { 84 | Event::NewEvents(_) => { 85 | let now = Instant::now(); 86 | imgui.io_mut().update_delta_time(now - last_frame); 87 | last_frame = now; 88 | } 89 | Event::MainEventsCleared => { 90 | let gl_window = display.gl_window(); 91 | platform 92 | .prepare_frame(imgui.io_mut(), &gl_window.window()) 93 | .expect("Failed to start frame"); 94 | gl_window.window().request_redraw(); 95 | } 96 | Event::RedrawRequested(_) => { 97 | let mut ui = imgui.frame(); 98 | 99 | let mut run = true; 100 | run = run_ui( 101 | &mut run, 102 | &mut ui, 103 | display.get_context(), 104 | renderer.textures(), 105 | ); 106 | if !run { 107 | *control_flow = ControlFlow::Exit; 108 | } 109 | 110 | let gl_window = display.gl_window(); 111 | let mut target = display.draw(); 112 | target.clear_color(1.0, 1.0, 1.0, 1.0); 113 | platform.prepare_render(&ui, gl_window.window()); 114 | let draw_data = ui.render(); 115 | renderer 116 | .render(&mut target, draw_data) 117 | .expect("Rendering failed"); 118 | target.finish().expect("Failed to swap buffers"); 119 | } 120 | Event::WindowEvent { 121 | event: WindowEvent::CloseRequested, 122 | .. 123 | } => *control_flow = ControlFlow::Exit, 124 | event => { 125 | let gl_window = display.gl_window(); 126 | platform.handle_event(imgui.io_mut(), gl_window.window(), &event); 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/aflak_cake/src/cache.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicBool, AtomicUsize, Ordering}, 3 | Arc, 4 | }; 5 | use std::thread; 6 | use std::time::Instant; 7 | 8 | use crate::dst::{Output, TransformIdx}; 9 | use crate::timed::Timed; 10 | 11 | use chashmap::CHashMap; 12 | 13 | /// The Cache object used to run cached computations with cake 14 | /// 15 | /// The cache is a concurrent hash-map, that will stay alive after 16 | /// being dropped at least until the last worker using the cache releases it. 17 | #[derive(Debug)] 18 | pub struct Cache { 19 | cache: CHashMap>>, 20 | in_use: Arc, 21 | scheduled_for_destruction: Arc, 22 | } 23 | 24 | impl Default for Cache { 25 | fn default() -> Self { 26 | Self::new() 27 | } 28 | } 29 | 30 | pub struct CacheRef { 31 | inner: *const Cache, 32 | in_use: Arc, 33 | scheduled_for_destruction: Arc, 34 | } 35 | 36 | impl Clone for CacheRef { 37 | fn clone(&self) -> Self { 38 | Self { 39 | inner: self.inner, 40 | in_use: self.in_use.clone(), 41 | scheduled_for_destruction: self.scheduled_for_destruction.clone(), 42 | } 43 | } 44 | } 45 | 46 | impl CacheRef { 47 | /// Compute and insert in cache *or* get from cache. 48 | /// Return None if the cache is scheduled for destruction. 49 | /// 50 | /// If cached value is present and newer than the providedd instant, then 51 | /// do not do the heavy computation and return the cached value. 52 | pub(crate) fn compute( 53 | &self, 54 | t_idx: TransformIdx, 55 | t_instant: Instant, 56 | f: F, 57 | ) -> Option, Arc>>>> 58 | where 59 | F: FnOnce() -> Vec, Arc>>, 60 | { 61 | if self.scheduled_for_destruction.load(Ordering::Acquire) { 62 | None 63 | } else { 64 | self.in_use.fetch_add(1, Ordering::SeqCst); 65 | 66 | let ret = unsafe { (*self.inner).compute(t_idx, t_instant, f) }; 67 | 68 | self.in_use.fetch_sub(1, Ordering::SeqCst); 69 | 70 | Some(ret) 71 | } 72 | } 73 | } 74 | 75 | unsafe impl Sync for CacheRef {} 76 | unsafe impl Send for CacheRef {} 77 | 78 | #[derive(Debug)] 79 | struct CacheBox { 80 | time: Instant, 81 | values: Vec, Arc>>, 82 | } 83 | 84 | impl Cache { 85 | /// Initialize Cache 86 | pub fn new() -> Self { 87 | Self { 88 | cache: CHashMap::new(), 89 | in_use: Arc::new(AtomicUsize::new(0)), 90 | scheduled_for_destruction: Arc::new(AtomicBool::new(false)), 91 | } 92 | } 93 | 94 | /// Get currently cached value for given Input. 95 | /// The value may or may not have expired. 96 | pub fn get(&self, output: &Output) -> Option, Arc>> { 97 | if let Some(some_cache_box) = self.cache.get(&output.t_idx) { 98 | if let Some(ref cache_box) = *some_cache_box { 99 | return cache_box.values.get(output.index()).cloned(); 100 | } 101 | } 102 | None 103 | } 104 | 105 | pub(crate) fn get_ref(&self) -> CacheRef { 106 | CacheRef { 107 | inner: self, 108 | in_use: self.in_use.clone(), 109 | scheduled_for_destruction: self.scheduled_for_destruction.clone(), 110 | } 111 | } 112 | 113 | pub(crate) fn init>(&mut self, ids: I) { 114 | for id in ids { 115 | if !self.cache.contains_key(&id) { 116 | self.cache.insert_new(id, None); 117 | } 118 | } 119 | } 120 | 121 | pub(crate) fn compute( 122 | &self, 123 | t_idx: TransformIdx, 124 | t_instant: Instant, 125 | f: F, 126 | ) -> Timed, Arc>>> 127 | where 128 | F: FnOnce() -> Vec, Arc>>, 129 | { 130 | if let Some(some_cache_box) = self.cache.get(&t_idx) { 131 | if let Some(ref cache_box) = *some_cache_box { 132 | if cache_box.time >= t_instant { 133 | return Timed::from_instant(cache_box.values.clone(), cache_box.time); 134 | } 135 | } 136 | } 137 | 138 | let result = f(); 139 | 140 | let ret = result.clone(); 141 | let mut some_cache_box = self.cache.get_mut(&t_idx).unwrap(); 142 | *some_cache_box = Some(CacheBox { 143 | time: t_instant, 144 | values: result, 145 | }); 146 | Timed::from_instant(ret, t_instant) 147 | } 148 | } 149 | 150 | impl Drop for Cache { 151 | fn drop(&mut self) { 152 | self.scheduled_for_destruction 153 | .store(true, Ordering::Release); 154 | while self.in_use.load(Ordering::Acquire) > 0 { 155 | thread::yield_now(); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # aflak - Advanced Framework for Learning Astrophysical Knowledge 2 | //! 3 | extern crate clap; 4 | extern crate glium; 5 | extern crate imgui; 6 | extern crate imgui_glium_renderer; 7 | extern crate implot; 8 | extern crate owning_ref; 9 | 10 | extern crate aflak_cake as cake; 11 | extern crate aflak_imgui_glium_support as support; 12 | extern crate aflak_plot; 13 | extern crate aflak_primitives as primitives; 14 | extern crate imgui_file_explorer; 15 | extern crate imgui_tone_curve; 16 | extern crate node_editor; 17 | 18 | mod aflak; 19 | mod cli; 20 | mod constant_editor; 21 | mod file_dialog; 22 | mod layout; 23 | mod output_window; 24 | mod templates; 25 | 26 | use std::env; 27 | use std::fs; 28 | use std::io::{self, Read}; 29 | use std::path::PathBuf; 30 | use std::process; 31 | 32 | use node_editor::NodeEditor; 33 | 34 | use crate::aflak::Aflak; 35 | 36 | use implot::Context; 37 | 38 | const CLEAR_COLOR: [f32; 4] = [0.05, 0.05, 0.05, 1.0]; 39 | 40 | fn main() { 41 | let matches = cli::build_cli().version(version()).get_matches(); 42 | 43 | let import_data = match open_buffer(&matches) { 44 | Ok(buf) => buf, 45 | Err(e) => { 46 | if let Some(file_name) = matches.value_of("ron") { 47 | eprintln!("Error on opening file '{}': {}", file_name, e); 48 | } else { 49 | eprintln!("Error on opening buffer: {}", e); 50 | } 51 | process::exit(1) 52 | } 53 | }; 54 | 55 | let node_editor = match NodeEditor::from_export_buf(import_data) { 56 | Ok(mut editor) => { 57 | editor 58 | .valid_history 59 | .push(node_editor::event::ProvenanceEvent::Import( 60 | None, 61 | None, 62 | editor.dst.clone(), 63 | Ok(()), 64 | )); 65 | editor 66 | } 67 | Err(e) => { 68 | eprintln!("Import failed! Initialize empty node editor.\n{}", e); 69 | NodeEditor::default() 70 | } 71 | }; 72 | 73 | let mut aflak = Aflak::init(node_editor); 74 | 75 | let config = support::AppConfig { 76 | title: format!("aflak {}", env!("CARGO_PKG_VERSION")), 77 | clear_color: CLEAR_COLOR, 78 | ini_filename: Some(PathBuf::from("aflak.ini")), 79 | maximized: true, 80 | ..Default::default() 81 | }; 82 | let transformations_ref: Vec<_> = primitives::TRANSFORMATIONS.iter().collect(); 83 | let plotcontext = Context::create(); 84 | support::init(config).main_loop(move |ui, gl_ctx, textures| { 85 | let transformations = transformations_ref.as_slice(); 86 | aflak.main_menu_bar(ui); 87 | aflak.node_editor(ui, transformations); 88 | aflak.output_windows(ui, gl_ctx, textures, &plotcontext); 89 | aflak.show_errors(ui); 90 | aflak.file_dialog(ui); 91 | if aflak.show_metrics { 92 | ui.show_metrics_window(&mut aflak.show_metrics); 93 | } 94 | if aflak.show_bind_manager { 95 | aflak.bind_manager(ui); 96 | } 97 | !aflak.quit 98 | }) 99 | } 100 | 101 | /// Clean up path from user input. 102 | /// Make local path absolute and attempt to canonize it. 103 | fn path_clean_up(path: Option<&str>, default: &str) -> PathBuf { 104 | let path = path.unwrap_or(default); 105 | let path = PathBuf::from(path); 106 | let path = if path.is_absolute() { 107 | path 108 | } else { 109 | let pwd = env::current_dir().unwrap_or_default(); 110 | pwd.join(path) 111 | }; 112 | path.canonicalize().unwrap_or(path) 113 | } 114 | 115 | fn open_buffer(matches: &clap::ArgMatches) -> Result, io::Error> { 116 | let fits = matches.value_of("fits"); 117 | let fits_path = path_clean_up(fits, "file.fits"); 118 | if let Some(template_name) = matches.value_of("template") { 119 | if let Some(template) = templates::select_template(template_name, fits_path) { 120 | Ok(Box::new(template)) 121 | } else { 122 | unreachable!("Got '{}', an unexpected result.", template_name) 123 | } 124 | } else if let Some(ron_file) = matches.value_of("ron") { 125 | if ron_file == "-" { 126 | Ok(Box::new(StdinReader::default())) 127 | } else { 128 | let file = fs::File::open(ron_file)?; 129 | Ok(Box::new(file)) 130 | } 131 | } else { 132 | // Fall back to default template 133 | let default_template = templates::show_frame_and_wave(fits_path); 134 | Ok(Box::new(default_template)) 135 | } 136 | } 137 | 138 | struct StdinReader { 139 | r: io::Stdin, 140 | } 141 | 142 | impl Default for StdinReader { 143 | fn default() -> Self { 144 | Self { r: io::stdin() } 145 | } 146 | } 147 | 148 | impl io::Read for StdinReader { 149 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 150 | let mut lock = self.r.lock(); 151 | lock.read(buf) 152 | } 153 | } 154 | 155 | fn version() -> &'static str { 156 | concat!( 157 | env!("CARGO_PKG_VERSION"), 158 | include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")) 159 | ) 160 | } 161 | -------------------------------------------------------------------------------- /src/aflak_cake/tests/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate variant_name_derive; 3 | extern crate variant_name; 4 | #[macro_use] 5 | extern crate lazy_static; 6 | extern crate aflak_cake; 7 | 8 | #[macro_use] 9 | extern crate serde; 10 | extern crate ron; 11 | 12 | extern crate boow; 13 | 14 | use boow::Bow; 15 | 16 | mod support; 17 | use crate::support::*; 18 | 19 | fn make_macro() -> aflak_cake::macros::MacroHandle<'static, AlgoIO, E> { 20 | if let &[plus1, minus1, _, _, _] = *TRANSFORMATIONS_REF { 21 | // An arrow points from a box's input to a box's output `OUT -> INT` 22 | // We build the dst as follows (all functions are trivial and only have 1 output or 0/1 input): 23 | // 0 (default input) ---\ 24 | //EMPTY c, plus1 -> d, plus1 -> OUT1 25 | // \-> b, minus1 -> OUT2 \-> e, plus1 26 | let mut dst = DST::new(); 27 | let b = dst.add_transform(&minus1, None); 28 | let c = dst.add_transform(&plus1, None); 29 | let d = dst.add_transform(&plus1, None); 30 | let e = dst.add_transform(&plus1, None); 31 | let _out1 = dst.attach_output(Output::new(d, 0)).unwrap(); 32 | let _out2 = dst.attach_output(Output::new(b, 0)).unwrap(); 33 | dst.connect(Output::new(c, 0), Input::new(e, 0)).unwrap(); 34 | dst.connect(Output::new(c, 0), Input::new(d, 0)).unwrap(); 35 | 36 | let mut manager = aflak_cake::macros::MacroManager::new(); 37 | let macr = manager.create_macro(); 38 | *macr.write().dst_mut() = dst; 39 | macr.clone() 40 | } else { 41 | unreachable!() 42 | } 43 | } 44 | 45 | #[test] 46 | fn test_run_macros() { 47 | let macr = make_macro(); 48 | 49 | let got_outputs: Vec<_> = macr 50 | .call(vec![Bow::Owned(AlgoIO::Integer(1))]) 51 | .into_iter() 52 | .map(|r| r.unwrap()) 53 | .collect(); 54 | assert_eq!(got_outputs, vec![AlgoIO::Integer(2), AlgoIO::Integer(0)]); 55 | } 56 | 57 | #[test] 58 | fn test_macro_inputs() { 59 | let macr = make_macro(); 60 | 61 | assert_eq!( 62 | macr.inputs(), 63 | vec![ 64 | aflak_cake::TransformInputSlot { 65 | type_id: TypeId("Integer"), 66 | default: None, 67 | name: "i", 68 | }, 69 | aflak_cake::TransformInputSlot { 70 | type_id: TypeId("Integer"), 71 | default: Some(AlgoIO::Integer(0)), 72 | name: "i", 73 | }, 74 | ] 75 | ) 76 | } 77 | 78 | #[test] 79 | fn test_macro_inputs_overwrite_correct_type() { 80 | let macr = make_macro(); 81 | let macr_id = macr.id(); 82 | let mut dst = DST::new(); 83 | let t_idx = dst.add_owned_transform(aflak_cake::Transform::from_macro(macr), Some(macr_id)); 84 | { 85 | let mut inputs = dst.get_default_inputs_mut(t_idx).unwrap(); 86 | inputs.write(1, AlgoIO::Integer(2)); 87 | } 88 | 89 | // Get overwritten input if type aggrees with macro's expected input type 90 | assert_eq!( 91 | dst.get_default_inputs(t_idx).unwrap().to_owned(), 92 | vec![None, Some(AlgoIO::Integer(2))], 93 | ) 94 | } 95 | 96 | #[test] 97 | fn test_macro_inputs_overwrite_wrong_type() { 98 | let macr = make_macro(); 99 | let macr_id = macr.id(); 100 | let mut dst = DST::new(); 101 | let t_idx = dst.add_owned_transform(aflak_cake::Transform::from_macro(macr), Some(macr_id)); 102 | { 103 | let mut inputs = dst.get_default_inputs_mut(t_idx).unwrap(); 104 | inputs.write(1, AlgoIO::Image2d(vec![])); 105 | } 106 | 107 | // Get macro's original input if overwritten input is of incorrect type 108 | assert_eq!( 109 | dst.get_default_inputs(t_idx).unwrap().to_owned(), 110 | vec![None, Some(AlgoIO::Integer(0))], 111 | ) 112 | } 113 | 114 | #[test] 115 | fn test_macro_standalone_serde() { 116 | let macr = make_macro(); 117 | let serde = aflak_cake::macros::SerdeMacroStandAlone::from(¯); 118 | let out = ron::ser::to_string_pretty(&serde, Default::default()).unwrap(); 119 | 120 | let back: aflak_cake::macros::SerdeMacroStandAlone = ron::de::from_str(&out).unwrap(); 121 | let recreated_macr = aflak_cake::macros::MacroHandle::from(back.into_macro().unwrap()); 122 | let serde2 = aflak_cake::macros::SerdeMacroStandAlone::from(&recreated_macr); 123 | let out2 = ron::ser::to_string_pretty(&serde2, Default::default()).unwrap(); 124 | 125 | assert_eq!(out, out2); 126 | } 127 | 128 | #[test] 129 | fn test_nested_macro_standalone_serde() { 130 | let macr = make_macro(); 131 | { 132 | let macr_id = macr.id(); 133 | let nested_macro = aflak_cake::macros::MacroManager::new() 134 | .create_macro() 135 | .clone(); 136 | macr.write().dst_mut().add_owned_transform( 137 | aflak_cake::Transform::from_macro(nested_macro), 138 | Some(macr_id), 139 | ); 140 | } 141 | 142 | let serde = aflak_cake::macros::SerdeMacroStandAlone::from(¯); 143 | let out = ron::ser::to_string_pretty(&serde, Default::default()).unwrap(); 144 | 145 | let back: aflak_cake::macros::SerdeMacroStandAlone = ron::de::from_str(&out).unwrap(); 146 | let recreated_macr = aflak_cake::macros::MacroHandle::from(back.into_macro().unwrap()); 147 | let serde2 = aflak_cake::macros::SerdeMacroStandAlone::from(&recreated_macr); 148 | let out2 = ron::ser::to_string_pretty(&serde2, Default::default()).unwrap(); 149 | 150 | assert_eq!(out, out2); 151 | } 152 | -------------------------------------------------------------------------------- /src/imgui_file_explorer/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate imgui; 2 | #[macro_use] 3 | extern crate cfg_if; 4 | 5 | use std::cmp::Ordering; 6 | use std::env; 7 | use std::path::*; 8 | use std::{fs, io}; 9 | 10 | use imgui::*; 11 | 12 | pub trait UiFileExplorer { 13 | /// Can filter over several extensions. 14 | /// Anything that can be treated as a reference to a string `AsRef` can be used as argument! 15 | /// Return the selected path, if any. 16 | fn file_explorer( 17 | &self, 18 | target: T, 19 | extensions: &[S], 20 | ) -> io::Result<(Option, Option)> 21 | where 22 | T: AsRef, 23 | S: AsRef; 24 | } 25 | 26 | fn has_extension, S: AsRef>(path: P, extensions: &[S]) -> bool { 27 | let path = path.as_ref(); 28 | if let Some(test_ext) = path.extension() { 29 | if let Some(test_ext) = test_ext.to_str() { 30 | extensions 31 | .iter() 32 | .any(|ext| test_ext.to_lowercase() == ext.as_ref().to_lowercase()) 33 | } else { 34 | false 35 | } 36 | } else { 37 | false 38 | } 39 | } 40 | 41 | fn is_hidden>(path: P) -> bool { 42 | let path = path.as_ref(); 43 | if let Some(name) = path.file_name() { 44 | name.to_string_lossy().starts_with('.') 45 | } else { 46 | false 47 | } 48 | } 49 | 50 | cfg_if! { 51 | if #[cfg(unix)] { 52 | pub const TOP_FOLDER: &str = "/"; 53 | pub const CURRENT_FOLDER: &str = "./"; 54 | } else { 55 | pub const TOP_FOLDER: &str = "C:"; 56 | pub const CURRENT_FOLDER: &str = "./"; 57 | } 58 | } 59 | 60 | /// Ui extends 61 | impl<'ui> UiFileExplorer for Ui<'ui> { 62 | fn file_explorer( 63 | &self, 64 | target: T, 65 | extensions: &[S], 66 | ) -> io::Result<(Option, Option)> 67 | where 68 | T: AsRef, 69 | S: AsRef, 70 | { 71 | let current_dir = env::current_dir().unwrap_or_else(|_| target.as_ref().to_owned()); 72 | TreeNode::new(format!("./")) 73 | .opened(false, Condition::Always) 74 | .build(&self, || {}); 75 | if self.is_item_clicked() { 76 | return Ok((None, Some(PathBuf::from("./")))); 77 | } 78 | view_dirs(&self, target, extensions, ¤t_dir) 79 | } 80 | } 81 | 82 | fn view_dirs<'a, T: AsRef, S: AsRef>( 83 | ui: &Ui<'a>, 84 | target: T, 85 | extensions: &[S], 86 | default_dir: &Path, 87 | ) -> io::Result<(Option, Option)> { 88 | let target = target.as_ref(); 89 | let mut files = Vec::new(); 90 | 91 | for direntry in fs::read_dir(target)? { 92 | match direntry { 93 | Ok(direntry) => { 94 | let path = direntry.path(); 95 | if !is_hidden(&path) && (path.is_dir() || has_extension(&path, extensions)) { 96 | files.push(path); 97 | } 98 | } 99 | Err(e) => eprintln!("Error on listing files: {:?}", e), 100 | } 101 | } 102 | 103 | // Sort folder first 104 | files.sort_by(|path1, path2| match (path1.is_dir(), path2.is_dir()) { 105 | (true, true) => path1.cmp(path2), 106 | (false, false) => path1.cmp(path2), 107 | (true, false) => Ordering::Less, 108 | (false, true) => Ordering::Greater, 109 | }); 110 | 111 | let mut ret = Ok((None, None)); 112 | let mut selected_path = None; 113 | let mut clicked_dir = None; 114 | for i in files { 115 | if i.is_dir() { 116 | if let Some(dirname) = i.file_name() { 117 | if let Some(dirname) = dirname.to_str() { 118 | let im_dirname = ImString::new(dirname); 119 | let open = default_dir.starts_with(&i); 120 | TreeNode::new(&im_dirname) 121 | .opened(open, Condition::Once) 122 | .build(&ui, || { 123 | if ui.is_item_clicked() { 124 | clicked_dir = Some(i.clone()); 125 | } 126 | let out = view_dirs(ui, &i, extensions, default_dir); 127 | match ret { 128 | // Do not overwrite return value when it is already set 129 | Ok((Some(_), _)) | Ok((_, Some(_))) => (), 130 | _ => { 131 | ret = out; 132 | } 133 | } 134 | }); 135 | if ui.is_item_clicked() { 136 | clicked_dir = Some(i.clone()); 137 | } 138 | } else { 139 | eprintln!("Could not get str out of directory: {:?}", i); 140 | } 141 | } else { 142 | eprintln!("Could not get dirname for path: {:?}", i); 143 | } 144 | } else if let Some(file_name) = i.file_name() { 145 | if let Some(file_name) = file_name.to_str() { 146 | ui.bullet_text(format!("")); 147 | ui.same_line(); 148 | if ui.small_button(&ImString::new(file_name)) { 149 | selected_path = Some(i.clone()); 150 | } 151 | } else { 152 | eprintln!("Could not get str out of file: {:?}", i); 153 | } 154 | } else { 155 | eprintln!("Could not get file_name for path: {:?}", i); 156 | } 157 | } 158 | ret = match ret { 159 | // Do not overwrite return value when it is already set 160 | Ok((Some(s), Some(c))) => Ok((Some(s), Some(c))), 161 | Ok((Some(s), None)) => Ok((Some(s), clicked_dir)), 162 | Ok((None, Some(c))) => Ok((selected_path, Some(c))), 163 | _ => Ok((selected_path, clicked_dir)), 164 | }; 165 | ret 166 | } 167 | -------------------------------------------------------------------------------- /src/imgui_glium_support/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate clipboard; 2 | extern crate glium; 3 | 4 | extern crate imgui; 5 | extern crate imgui_glium_renderer; 6 | extern crate imgui_winit_support; 7 | 8 | use glium::backend::{self, Facade}; 9 | use glium::glutin; 10 | use glium::glutin::event::{Event, WindowEvent}; 11 | use glium::glutin::event_loop::{ControlFlow, EventLoop}; 12 | use glium::glutin::window::WindowBuilder; 13 | use glium::{Display, Surface}; 14 | use imgui::{Context, FontConfig, FontSource, Textures, Ui}; 15 | use imgui_glium_renderer::{Renderer, Texture}; 16 | use imgui_winit_support::{HiDpiMode, WinitPlatform}; 17 | use std::path::PathBuf; 18 | use std::rc::Rc; 19 | use std::time::Instant; 20 | 21 | mod clipboard_support; 22 | 23 | #[derive(Clone, Debug)] 24 | pub struct AppConfig { 25 | pub title: String, 26 | pub clear_color: [f32; 4], 27 | pub ini_filename: Option, 28 | pub log_filename: Option, 29 | pub window_width: u32, 30 | pub window_height: u32, 31 | pub maximized: bool, 32 | } 33 | 34 | impl Default for AppConfig { 35 | fn default() -> Self { 36 | Self { 37 | title: "Default title".to_string(), 38 | clear_color: [1.0, 1.0, 1.0, 1.0], 39 | ini_filename: None, 40 | log_filename: None, 41 | window_width: 1024, 42 | window_height: 768, 43 | maximized: false, 44 | } 45 | } 46 | } 47 | 48 | pub struct System { 49 | pub event_loop: EventLoop<()>, 50 | pub display: glium::Display, 51 | pub imgui: Context, 52 | pub platform: WinitPlatform, 53 | pub renderer: Renderer, 54 | pub font_size: f32, 55 | pub clear_color: [f32; 4], 56 | } 57 | 58 | pub fn init(config: AppConfig) -> System { 59 | let title = match config.title.rfind('/') { 60 | Some(idx) => config.title.split_at(idx + 1).1, 61 | None => config.title.as_str(), 62 | }; 63 | let event_loop = EventLoop::new(); 64 | let context = glutin::ContextBuilder::new().with_vsync(true); 65 | let builder = WindowBuilder::new() 66 | .with_title(title.to_owned()) 67 | .with_inner_size(glutin::dpi::LogicalSize::new( 68 | config.window_width as f64, 69 | config.window_height as f64, 70 | )) 71 | .with_maximized(config.maximized); 72 | let display = 73 | Display::new(builder, context, &event_loop).expect("Failed to initialize display"); 74 | 75 | let mut imgui = Context::create(); 76 | imgui.set_ini_filename(config.ini_filename); 77 | imgui.set_log_filename(config.log_filename); 78 | 79 | if let Some(backend) = clipboard_support::init() { 80 | imgui.set_clipboard_backend(backend); 81 | } else { 82 | eprintln!("Failed to initialize clipboard"); 83 | } 84 | 85 | let mut platform = WinitPlatform::init(&mut imgui); 86 | { 87 | let gl_window = display.gl_window(); 88 | let window = gl_window.window(); 89 | platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded); 90 | } 91 | 92 | let hidpi_factor = platform.hidpi_factor(); 93 | let font_size = (13.0 * hidpi_factor) as f32; 94 | imgui.fonts().add_font(&[FontSource::DefaultFontData { 95 | config: Some(FontConfig { 96 | size_pixels: font_size, 97 | ..FontConfig::default() 98 | }), 99 | }]); 100 | 101 | imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; 102 | 103 | let renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer"); 104 | 105 | System { 106 | event_loop, 107 | display, 108 | imgui, 109 | platform, 110 | renderer, 111 | font_size, 112 | clear_color: config.clear_color, 113 | } 114 | } 115 | 116 | impl System { 117 | pub fn main_loop< 118 | F: FnMut(&mut Ui, &Rc, &mut Textures) -> bool + 'static, 119 | >( 120 | self, 121 | mut run_ui: F, 122 | ) { 123 | let System { 124 | event_loop, 125 | display, 126 | mut imgui, 127 | mut platform, 128 | mut renderer, 129 | clear_color, 130 | .. 131 | } = self; 132 | let mut last_frame = Instant::now(); 133 | 134 | event_loop.run(move |event, _, control_flow| match event { 135 | Event::NewEvents(_) => { 136 | let now = Instant::now(); 137 | imgui.io_mut().update_delta_time(now - last_frame); 138 | last_frame = now; 139 | } 140 | Event::MainEventsCleared => { 141 | let gl_window = display.gl_window(); 142 | platform 143 | .prepare_frame(imgui.io_mut(), &gl_window.window()) 144 | .expect("Failed to prepare frame"); 145 | gl_window.window().request_redraw(); 146 | } 147 | Event::RedrawRequested(_) => { 148 | let mut ui = imgui.frame(); 149 | 150 | let run = run_ui(&mut ui, display.get_context(), renderer.textures()); 151 | if !run { 152 | *control_flow = ControlFlow::Exit; 153 | } 154 | 155 | let gl_window = display.gl_window(); 156 | let mut target = display.draw(); 157 | target.clear_color( 158 | clear_color[0], 159 | clear_color[1], 160 | clear_color[2], 161 | clear_color[3], 162 | ); 163 | platform.prepare_render(&ui, gl_window.window()); 164 | let draw_data = ui.render(); 165 | renderer 166 | .render(&mut target, draw_data) 167 | .expect("Rendering failed"); 168 | target.finish().expect("Failed to swap buffers"); 169 | } 170 | Event::WindowEvent { 171 | event: WindowEvent::CloseRequested, 172 | .. 173 | } => *control_flow = ControlFlow::Exit, 174 | event => { 175 | let gl_window = display.gl_window(); 176 | platform.handle_event(imgui.io_mut(), gl_window.window(), &event); 177 | } 178 | }) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/aflak_cake/tests/dst.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate variant_name_derive; 3 | extern crate variant_name; 4 | #[macro_use] 5 | extern crate lazy_static; 6 | extern crate aflak_cake; 7 | extern crate futures; 8 | 9 | #[macro_use] 10 | extern crate serde; 11 | extern crate ron; 12 | 13 | mod support; 14 | use crate::support::*; 15 | 16 | use aflak_cake::Cache; 17 | use futures::{future::Future, Async}; 18 | use ron::de; 19 | use ron::ser; 20 | 21 | fn get_all_transforms() -> [&'static Transform<'static, AlgoIO, E>; 5] { 22 | [ 23 | Box::leak(Box::new(get_plus1_transform())), 24 | Box::leak(Box::new(get_minus1_transform())), 25 | Box::leak(Box::new(get_get1_transform())), 26 | Box::leak(Box::new(get_get_image_transform())), 27 | Box::leak(Box::new(get_divide_by_10_transform())), 28 | ] 29 | } 30 | 31 | macro_rules! assert_output_eq { 32 | ($dst: expr, $output: expr, $expected_value: expr, $cache: expr) => {{ 33 | let mut promise = $dst.compute($output, $cache); 34 | let out = loop { 35 | match promise.poll() { 36 | Ok(Async::Ready(r)) => break r, 37 | Ok(Async::NotReady) => ::std::thread::yield_now(), 38 | Err(e) => panic!("Fails: {}", e), 39 | } 40 | }; 41 | assert_eq!(**out, $expected_value); 42 | }}; 43 | } 44 | 45 | #[test] 46 | fn test_make_dst_and_iterate_dependencies() { 47 | let [plus1, minus1, get1, _image, _] = get_all_transforms(); 48 | 49 | // An arrow points from a box's input to a box's output `OUT -> INT` 50 | // We build the dst as follows (all functions are trivial and only have 1 output or 0/1 input): 51 | // a, get1 -------------------> c, plus1 -> d, plus1 -> OUT1 52 | // \-> b, minus1 -> OUT2 \-> e, plus1 53 | let mut dst = DST::new(); 54 | let a = dst.add_transform(&get1, None); 55 | let b = dst.add_transform(&minus1, None); 56 | let c = dst.add_transform(&plus1, None); 57 | let d = dst.add_transform(&plus1, None); 58 | let e = dst.add_transform(&plus1, None); 59 | let out1 = dst.attach_output(Output::new(d, 0)).unwrap(); 60 | let out2 = dst.attach_output(Output::new(b, 0)).unwrap(); 61 | dst.connect(Output::new(a, 0), Input::new(c, 0)).unwrap(); 62 | dst.connect(Output::new(a, 0), Input::new(b, 0)).unwrap(); 63 | dst.connect(Output::new(c, 0), Input::new(e, 0)).unwrap(); 64 | dst.connect(Output::new(c, 0), Input::new(d, 0)).unwrap(); 65 | 66 | // Serialize and unserialize DST 67 | let s = ser::to_string(&dst).unwrap(); 68 | let dst: DST = de::from_str(&s).unwrap(); 69 | 70 | let mut cache = Cache::new(); 71 | 72 | assert_output_eq!(dst, out1, AlgoIO::Integer(3), &mut cache); 73 | assert_output_eq!(dst, out2, AlgoIO::Integer(0), &mut cache); 74 | } 75 | 76 | #[test] 77 | fn test_connect_wrong_types() { 78 | let [plus1, _minus1, _get1, image, _] = get_all_transforms(); 79 | 80 | // An arrow points from a box's input to a box's output `OUT -> INT` 81 | // We build the dst as follows (all functions are trivial and only have 1 output or 0/1 input): 82 | // a, image 83 | // \-> b, plus1 -> OUT1 84 | let mut dst = DST::new(); 85 | let a = dst.add_transform(&image, None); 86 | let b = dst.add_transform(&plus1, None); 87 | let _out1 = dst.attach_output(Output::new(b, 0)).unwrap(); 88 | if let Err(DSTError::IncompatibleTypes(_)) = dst.connect(Output::new(a, 0), Input::new(b, 0)) { 89 | assert!(true); 90 | } else { 91 | assert!(false, "IncompatibleTypes expected!"); 92 | } 93 | } 94 | 95 | #[test] 96 | fn test_connect_convertible_types() { 97 | let [_plus1, _minus1, get1, _image, divide10] = get_all_transforms(); 98 | 99 | // An arrow points from a box's input to a box's output `OUT -> INT` 100 | // We build the dst as follows (all functions are trivial and only have 1 output or 0/1 input): 101 | // a, get1 102 | // \-> b, divide10 -> OUT1 103 | let mut dst = DST::new(); 104 | let a = dst.add_transform(&get1, None); 105 | let b = dst.add_transform(÷10, None); 106 | let _out1 = dst.attach_output(Output::new(b, 0)).unwrap(); 107 | assert!( 108 | dst.connect(Output::new(a, 0), Input::new(b, 0)).is_ok(), 109 | "Could not connnected convertible types" 110 | ); 111 | } 112 | 113 | #[test] 114 | fn test_cache_reset() { 115 | let [plus1, minus1, get1] = if let &[plus1, minus1, get1, _image, _] = *TRANSFORMATIONS_REF { 116 | [plus1, minus1, get1] 117 | } else { 118 | unreachable!() 119 | }; 120 | 121 | // a, get1 -------------------> c, plus1 -> d, plus1 -> OUT1 122 | // \-> b, minus1 -> OUT2 \-> e, plus1 123 | let mut dst = DST::new(); 124 | let a = dst.add_transform(&get1, None); 125 | let b = dst.add_transform(&minus1, None); 126 | let c = dst.add_transform(&plus1, None); 127 | let d = dst.add_transform(&plus1, None); 128 | let e = dst.add_transform(&plus1, None); 129 | let out1 = dst.attach_output(Output::new(d, 0)).unwrap(); 130 | let _out2 = dst.attach_output(Output::new(b, 0)).unwrap(); 131 | 132 | dst.connect(Output::new(a, 0), Input::new(c, 0)).unwrap(); 133 | dst.connect(Output::new(a, 0), Input::new(b, 0)).unwrap(); 134 | dst.connect(Output::new(c, 0), Input::new(e, 0)).unwrap(); 135 | dst.connect(Output::new(c, 0), Input::new(d, 0)).unwrap(); 136 | 137 | let mut cache = Cache::new(); 138 | 139 | assert_output_eq!(dst, out1, AlgoIO::Integer(3), &mut cache); 140 | // Connect b's output to c's input 141 | dst.connect(Output::new(b, 0), Input::new(c, 0)).unwrap(); 142 | assert_output_eq!(dst, out1, AlgoIO::Integer(2), &mut cache); 143 | } 144 | 145 | #[test] 146 | fn test_remove_node() { 147 | let [plus1, minus1, get1, _image, _] = get_all_transforms(); 148 | 149 | // a, get1 -------------------> c, plus1 -> d, plus1 -> OUT1 150 | // \-> b, minus1 -> OUT2 \-> e, plus1 151 | 152 | let mut dst = DST::new(); 153 | let a = dst.add_transform(&get1, None); 154 | let b = dst.add_transform(&minus1, None); 155 | let c = dst.add_transform(&plus1, None); 156 | let d = dst.add_transform(&plus1, None); 157 | let e = dst.add_transform(&plus1, None); 158 | 159 | let d_out0 = Output::new(d, 0); 160 | let b_out0 = Output::new(b, 0); 161 | 162 | let out1 = dst.attach_output(d_out0).unwrap(); 163 | let out2 = dst.attach_output(b_out0).unwrap(); 164 | 165 | let a_out0 = Output::new(a, 0); 166 | let b_in0 = Input::new(b, 0); 167 | 168 | dst.connect(a_out0, Input::new(c, 0)).unwrap(); 169 | dst.connect(a_out0, b_in0).unwrap(); 170 | dst.connect(Output::new(c, 0), Input::new(e, 0)).unwrap(); 171 | dst.connect(Output::new(c, 0), Input::new(d, 0)).unwrap(); 172 | 173 | // We will remove "c" 174 | // After removal, the graph will be come as below 175 | // 176 | // a, get1 d, plus1 -> OUT1 177 | // \-> b, minus1 -> OUT2 e, plus1 178 | 179 | dst.remove_transform(c); 180 | let mut links: Vec<_> = dst.links_iter().collect(); 181 | links.sort(); 182 | assert_eq!(links, { 183 | let mut vec = vec![ 184 | (&a_out0, aflak_cake::InputSlot::Transform(b_in0)), 185 | (&d_out0, aflak_cake::InputSlot::Output(out1)), 186 | (&b_out0, aflak_cake::InputSlot::Output(out2)), 187 | ]; 188 | vec.sort(); 189 | vec 190 | }); 191 | } 192 | -------------------------------------------------------------------------------- /src/aflak_cake/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # aflak - Computational mAKE 2 | //! 3 | //! A crate to manage a graph of interdependent functions. 4 | //! 5 | //! To define a new transformation (i.e. a node in a node graph), for example for 6 | //! an existing project (i.e. aflak), please see the 7 | //! [cake_transform!](macro.cake_transform.html) macro. 8 | extern crate chashmap; 9 | extern crate futures; 10 | extern crate rayon; 11 | 12 | extern crate boow; 13 | extern crate serde; 14 | #[macro_use] 15 | extern crate serde_derive; 16 | pub extern crate uuid; 17 | extern crate variant_name; 18 | 19 | mod cache; 20 | mod dst; 21 | pub mod export; 22 | mod future; 23 | pub mod macros; 24 | mod timed; 25 | mod transform; 26 | 27 | pub use crate::cache::Cache; 28 | pub use crate::dst::{ 29 | compute, DSTError, Input, InputDefaultsMut, InputSlot, LinkIter, MetaTransform, Node, NodeId, 30 | NodeIter, Output, OutputId, TransformAndDefaults, TransformIdx, DST, 31 | }; 32 | pub use crate::export::{DeserDST, ImportError, NamedAlgorithms, SerialDST}; 33 | pub use crate::future::Task; 34 | pub use crate::timed::Timed; 35 | pub use crate::transform::*; 36 | pub use boow::Bow; 37 | pub use futures::{future::Future, Async}; 38 | 39 | pub use self::variant_name::VariantName; 40 | 41 | /// Trait to define a default value for each variant of an enumeration. 42 | pub trait DefaultFor: VariantName { 43 | fn default_for(variant_name: &str) -> Self; 44 | } 45 | 46 | /// Trait to discriminate editable variants from constant variants of an 47 | /// enumeration. 48 | /// 49 | /// Especially used for a node editor. 50 | pub trait EditableVariants: VariantName { 51 | /// Get list of editable variants. 52 | fn editable_variants() -> &'static [&'static str]; 53 | /// Check if given variant is editable or not. 54 | fn editable(variant_name: &str) -> bool { 55 | Self::editable_variants().contains(&variant_name) 56 | } 57 | } 58 | 59 | /// Represent how the variant with the name defined in `from` can be converted 60 | /// to another variant whose name is defined in `into`. 61 | pub struct ConvertibleVariant { 62 | /// Variant name that can be converted 63 | pub from: &'static str, 64 | /// Target variant name 65 | pub into: &'static str, 66 | /// Conversion function. Cannot fail. 67 | pub f: fn(&T) -> T, 68 | } 69 | 70 | /// Trait implemented to define conversions between different variants. 71 | /// 72 | /// To implement this trait, please define a conversation table from and into 73 | /// the different variants of an enumeration. 74 | pub trait ConvertibleVariants: VariantName + Sized + 'static { 75 | /// Definition of conversions. This table is user-defined and must be 76 | /// implemented. 77 | const CONVERTION_TABLE: &'static [ConvertibleVariant]; 78 | 79 | /// Convert variant `from` into variant `into`. Return `None` if `from` 80 | /// cannot be converted into `into`. 81 | fn convert<'a>( 82 | from: &'static str, 83 | into: &'static str, 84 | value: &'a Self, 85 | ) -> Option> { 86 | if from == into { 87 | Some(Bow::Borrowed(value)) 88 | } else if let Some(variant) = Self::CONVERTION_TABLE 89 | .iter() 90 | .find(|variant| variant.from == from && variant.into == into) 91 | { 92 | let out = (variant.f)(value); 93 | Some(Bow::Owned(out)) 94 | } else { 95 | None 96 | } 97 | } 98 | 99 | /// Check whether variant `from` can be converted into variant `into`. 100 | fn convertible(from: &'static str, into: &'static str) -> bool { 101 | if from == into { 102 | true 103 | } else if Self::CONVERTION_TABLE 104 | .iter() 105 | .find(|variant| variant.from == from && variant.into == into) 106 | .is_some() 107 | { 108 | true 109 | } else { 110 | false 111 | } 112 | } 113 | } 114 | 115 | /// Make it easier to define a function used for a transform. Used internally 116 | /// by [`cake_transform`]. You probably want to directly use [`cake_transform`]. 117 | #[doc(hidden)] 118 | #[macro_export] 119 | macro_rules! cake_fn { 120 | // Special case where no argument is provided 121 | ($fn_name: ident<$enum_name: ident, $err_type: ty>() $fn_block: block) => { 122 | fn $fn_name( 123 | _: Vec<&$enum_name>, 124 | ) -> Vec> { 125 | $fn_block 126 | } 127 | }; 128 | // Standard case 129 | ($fn_name: ident<$enum_name: ident, $err_type: ty>($($x: ident: $x_type: ident),*) $fn_block: block) => { 130 | fn $fn_name( 131 | input: Vec<$crate::Bow<$enum_name>>, 132 | ) -> Vec> { 133 | #[allow(non_camel_case_types)] 134 | enum Args { $($x,)* } 135 | if let ($(&$enum_name::$x_type(ref $x), )*) = ($(&*input[Args::$x as usize], )*) { 136 | $fn_block 137 | } else { 138 | panic!("Unexpected argument!") 139 | } 140 | } 141 | }; 142 | } 143 | 144 | /// Create a new transform from a rust function. 145 | /// 146 | /// # Example 147 | /// 148 | /// ```rust 149 | /// #[macro_use] extern crate variant_name_derive; 150 | /// #[macro_use] extern crate aflak_cake; 151 | /// use aflak_cake::*; 152 | /// 153 | /// #[derive(Clone, PartialEq, Debug, VariantName)] 154 | /// pub enum AlgoIO { 155 | /// Integer(u64), 156 | /// Image2d(Vec>), 157 | /// } 158 | /// 159 | /// pub enum E {} 160 | /// // _______ MAJOR/MINOR/PATCH version numbers 161 | /// let plus_one_trans = cake_transform!( // / / / 162 | /// "Long description of the transform", "kind", 1, 0, 0, 163 | /// // key identifying transformation Input arguments with default value (optional) 164 | /// // \ In/Out types /- Error type / _ Output type(s) 165 | /// // \ / / /------------/ / 166 | /// plus1(i: Integer = 0) -> Integer { 167 | /// // Define the body of the transformation. 168 | /// // Must return a Vec>! 169 | /// vec![Ok(AlgoIO::Integer(i + 1))] 170 | /// }); 171 | /// ``` 172 | #[macro_export] 173 | macro_rules! cake_transform { 174 | ($description: expr, $kind:expr, $major: expr, $minor: expr, $patch: expr, $fn_name: ident<$enum_name: ident, $err_type: ty>($($x: ident: $x_type: ident $(= $x_default_val: expr), *),*) -> $($out_type: ident),* $fn_block: block) => {{ 175 | cake_fn!{$fn_name<$enum_name, $err_type>($($x: $x_type),*) $fn_block} 176 | 177 | $crate::Transform::from_algorithm($crate::Algorithm::Function { 178 | f: $fn_name, 179 | id: $crate::FnTransformId(stringify!($fn_name)), 180 | version: $crate::Version { 181 | major: $major, 182 | minor: $minor, 183 | patch: $patch 184 | }, 185 | kind: $kind, 186 | description: $description, 187 | inputs: vec![$( 188 | $crate::TransformInputSlot { 189 | type_id: $crate::TypeId(stringify!($x_type)), 190 | default: cake_some_first_value!($( $enum_name::$x_type($x_default_val) ),*), 191 | name: stringify!($x), 192 | }, )*], 193 | outputs: vec![$($crate::TypeId(stringify!($out_type)), )*], 194 | }) 195 | }}; 196 | } 197 | 198 | /// Helper macro for internal use. 199 | #[doc(hidden)] 200 | #[macro_export] 201 | macro_rules! cake_some_first_value { 202 | () => { 203 | None 204 | }; 205 | ($x:expr) => { 206 | Some($x) 207 | }; 208 | ($x:expr, $($xs:expr)+) => { 209 | compile_error!("Only zero or one value is expected.") 210 | }; 211 | } 212 | -------------------------------------------------------------------------------- /src/src/constant_editor.rs: -------------------------------------------------------------------------------- 1 | use crate::primitives::{self, IOValue}; 2 | use imgui_file_explorer::{UiFileExplorer, CURRENT_FOLDER}; 3 | use imgui_tone_curve::UiToneCurve; 4 | use node_editor::ConstantEditor; 5 | 6 | use imgui::{ChildWindow, DrawListMut, Id, Ui}; 7 | 8 | #[derive(Default)] 9 | pub struct MyConstantEditor; 10 | 11 | impl ConstantEditor for MyConstantEditor { 12 | fn editor<'a, I>( 13 | &self, 14 | ui: &Ui, 15 | constant: &IOValue, 16 | id: I, 17 | read_only: bool, 18 | draw_list: &DrawListMut, 19 | ) -> Option 20 | where 21 | I: Into>, 22 | { 23 | let id_stack = ui.push_id(id); 24 | 25 | let mut some_new_value = None; 26 | 27 | ui.group(|| some_new_value = inner_editor(ui, constant, read_only, &draw_list)); 28 | 29 | if read_only && ui.is_item_hovered() { 30 | ui.tooltip_text("Read only, value is set by input!"); 31 | } 32 | 33 | id_stack.pop(); 34 | 35 | some_new_value 36 | } 37 | } 38 | 39 | fn inner_editor( 40 | ui: &Ui, 41 | constant: &IOValue, 42 | read_only: bool, 43 | draw_list: &DrawListMut, 44 | ) -> Option { 45 | match *constant { 46 | IOValue::Str(ref string) => { 47 | let mut out = String::with_capacity(1024); 48 | out.push_str(string); 49 | let changed = ui 50 | .input_text(format!("String value"), &mut out) 51 | .read_only(read_only) 52 | .build(); 53 | if changed { 54 | Some(IOValue::Str(out.to_owned())) 55 | } else { 56 | None 57 | } 58 | } 59 | IOValue::Integer(ref int) => { 60 | use std::i32; 61 | const MIN: i64 = i32::MIN as i64; 62 | const MAX: i64 = i32::MAX as i64; 63 | 64 | if MIN <= *int && *int <= MAX { 65 | let mut out = *int as i32; 66 | let changed = ui 67 | .input_int(format!("Int value"), &mut out) 68 | .read_only(read_only) 69 | .build(); 70 | if changed { 71 | Some(IOValue::Integer(i64::from(out))) 72 | } else { 73 | None 74 | } 75 | } else { 76 | ui.text(format!( 77 | "Cannot edit integer smaller than {}\nor bigger than {}!\nGot {}.", 78 | MIN, MAX, int 79 | )); 80 | None 81 | } 82 | } 83 | IOValue::Float(ref float) => { 84 | let mut f = *float; 85 | if ui 86 | .input_float(format!("Float value"), &mut f) 87 | .read_only(read_only) 88 | .build() 89 | { 90 | Some(IOValue::Float(f)) 91 | } else { 92 | None 93 | } 94 | } 95 | IOValue::Float2(ref floats) => { 96 | let mut f2 = *floats; 97 | if ui 98 | .input_float2(format!("2 floats value"), &mut f2) 99 | .read_only(read_only) 100 | .build() 101 | { 102 | Some(IOValue::Float2(f2)) 103 | } else { 104 | None 105 | } 106 | } 107 | IOValue::ToneCurve(ref state) => { 108 | let mut state_ret = None; 109 | ChildWindow::new("tonecurve_fileexplorer") 110 | .size([430.0, 430.0]) 111 | .horizontal_scrollbar(true) 112 | .movable(false) 113 | .build(ui, || { 114 | let result = ui.tone_curve(&mut state.clone(), &draw_list); 115 | if let Ok(next_state) = result { 116 | let next_state = next_state.unwrap(); 117 | if &next_state != state { 118 | state_ret = Some(IOValue::ToneCurve(next_state)); 119 | } 120 | } 121 | }); 122 | state_ret 123 | } 124 | IOValue::Float3(ref floats) => { 125 | let mut f3 = *floats; 126 | if ui 127 | .input_float3(format!("3 floats value"), &mut f3) 128 | .read_only(read_only) 129 | .build() 130 | { 131 | Some(IOValue::Float3(f3)) 132 | } else { 133 | None 134 | } 135 | } 136 | IOValue::Float3x3(ref floats) => { 137 | let mut f3 = *floats; 138 | let mut t = true; 139 | for i in 0..3 { 140 | t = t & ui 141 | .input_float3(format!("3 floats value, {}", i), &mut f3[i]) 142 | .read_only(read_only) 143 | .build(); 144 | } 145 | if t { 146 | Some(IOValue::Float3x3(f3)) 147 | } else { 148 | None 149 | } 150 | } 151 | IOValue::Bool(ref b) => { 152 | let mut b = *b; 153 | if ui.checkbox(format!("Bool value"), &mut b) { 154 | Some(IOValue::Bool(b)) 155 | } else { 156 | None 157 | } 158 | } 159 | IOValue::Paths(ref file) => match file { 160 | primitives::PATHS::FileList(file) => { 161 | if read_only { 162 | None 163 | } else { 164 | let size = ui.item_rect_size(); 165 | let mut ret = Ok((None, None)); 166 | ChildWindow::new("path_fileexplorer") 167 | .size([size[0].max(400.0), 150.0]) 168 | .horizontal_scrollbar(true) 169 | .build(ui, || { 170 | ret = ui.file_explorer( 171 | CURRENT_FOLDER, 172 | &[ 173 | "fits", "fit", "fts", "cr2", "CR2", "RW2", "rw2", "nef", "NEF", 174 | ], 175 | ); 176 | }); 177 | ui.text(format!("Selected Files:")); 178 | for single_file in file { 179 | ui.text(single_file.to_str().unwrap_or("Unrepresentable path")); 180 | } 181 | if let Ok((Some(new_file), _)) = ret { 182 | let mut already_exist = false; 183 | let mut key = 0; 184 | for single_file in file { 185 | if *single_file == new_file { 186 | already_exist = true; 187 | break; 188 | } 189 | key += 1; 190 | } 191 | if already_exist { 192 | let mut new_files = file.clone(); 193 | new_files.remove(key); 194 | Some(IOValue::Paths(primitives::PATHS::FileList( 195 | new_files.to_vec(), 196 | ))) 197 | } else { 198 | let mut new_files = file.clone(); 199 | new_files.push(new_file); 200 | Some(IOValue::Paths(primitives::PATHS::FileList( 201 | new_files.to_vec(), 202 | ))) 203 | } 204 | } else { 205 | None 206 | } 207 | } 208 | } 209 | }, 210 | IOValue::Roi(ref roi) => { 211 | match roi { 212 | primitives::ROI::All => ui.text("Whole image"), 213 | primitives::ROI::PixelList(_) => { 214 | ui.text("Non-writable"); 215 | if ui.is_item_hovered() { 216 | ui.tooltip(|| { 217 | ui.text(" Please edit from output window "); 218 | }); 219 | } 220 | } 221 | }; 222 | None 223 | } 224 | _ => None, 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/aflak_plot/src/imshow/mod.rs: -------------------------------------------------------------------------------- 1 | //! Draw 2D images. 2 | mod hist; 3 | mod image; 4 | mod lut; 5 | mod state; 6 | mod zscale; 7 | 8 | pub extern crate aflak_cake as cake; 9 | pub extern crate aflak_primitives as primitives; 10 | pub extern crate node_editor; 11 | 12 | pub use self::interactions::InteractionId; 13 | pub use self::state::State; 14 | 15 | use std::borrow::Borrow; 16 | use std::collections::HashMap; 17 | 18 | use super::cake::TransformIdx; 19 | use super::node_editor::NodeEditor; 20 | use super::primitives::{IOErr, IOValue}; 21 | use glium::backend::Facade; 22 | use imgui::{self, TextureId, Ui}; 23 | use imgui_glium_renderer::Texture; 24 | use ndarray::ArrayD; 25 | 26 | use crate::err::Error; 27 | use crate::interactions; 28 | use crate::ticks; 29 | use crate::util; 30 | 31 | use super::AxisTransform; 32 | 33 | /// A handle to an OpenGL 2D texture. 34 | pub type Textures = imgui::Textures; 35 | type EditabaleValues = HashMap; 36 | type AflakNodeEditor = NodeEditor; 37 | 38 | impl<'ui> UiImage2d for Ui<'ui> { 39 | /// Show image given as input. 40 | /// 41 | /// The mutable reference `state` contains the current state of the user 42 | /// interaction with the window. 43 | /// 44 | /// # Example 45 | /// 46 | /// ```rust,no_run 47 | /// #[macro_use] extern crate imgui; 48 | /// extern crate aflak_imgui_glium_support as support; 49 | /// extern crate ndarray; 50 | /// extern crate aflak_plot; 51 | /// 52 | /// use std::time::Instant; 53 | /// use std::collections::HashMap; 54 | /// 55 | /// use imgui::{TextureId, Ui}; 56 | /// use ndarray::Array2; 57 | /// use aflak_plot::{ 58 | /// imshow::{self, UiImage2d}, 59 | /// AxisTransform, 60 | /// }; 61 | /// use imshow::cake::OutputId; 62 | /// use imshow::node_editor::NodeEditor; 63 | /// 64 | /// fn main() { 65 | /// let config = support::AppConfig { 66 | /// ..Default::default() 67 | /// }; 68 | /// let mut state = imshow::State::default(); 69 | /// support::init(Default::default()).main_loop(move |ui, gl_ctx, textures| { 70 | /// let texture_id = TextureId::from(1); 71 | /// if state.image_created_on().is_none() { 72 | /// let data = Array2::eye(10).into_dimensionality().unwrap(); 73 | /// if let Err(e) = state.set_image(data, Instant::now(), gl_ctx, texture_id, textures) { 74 | /// eprintln!("{:?}", e); 75 | /// return false; 76 | /// } 77 | /// } 78 | /// if let Err(e) = ui.image2d( 79 | /// gl_ctx, 80 | /// textures, 81 | /// texture_id, 82 | /// "", 83 | /// AxisTransform::none(), 84 | /// AxisTransform::none(), 85 | /// &mut state, 86 | /// &mut None, 87 | /// &mut HashMap::new(), 88 | /// &mut None, 89 | /// OutputId::new(0), 90 | /// &NodeEditor::default(), 91 | /// ) { 92 | /// eprintln!("{:?}", e); 93 | /// false 94 | /// } else { 95 | /// true 96 | /// } 97 | /// }) 98 | /// } 99 | /// ``` 100 | fn image2d( 101 | &self, 102 | ctx: &F, 103 | textures: &mut Textures, 104 | texture_id: TextureId, 105 | vunit: &str, 106 | xaxis: Option<&AxisTransform>, 107 | yaxis: Option<&AxisTransform>, 108 | state: &mut State, 109 | copying: &mut Option<(InteractionId, TransformIdx)>, 110 | store: &mut EditabaleValues, 111 | attaching: &mut Option<(cake::OutputId, TransformIdx, usize)>, 112 | outputid: cake::OutputId, 113 | node_editor: &AflakNodeEditor, 114 | ) -> Result<(), Error> 115 | where 116 | F: Facade, 117 | FX: Fn(f32) -> f32, 118 | FY: Fn(f32) -> f32, 119 | I: Borrow>, 120 | { 121 | let window_pos = self.window_pos(); 122 | let cursor_pos = self.cursor_screen_pos(); 123 | let window_size = self.window_size(); 124 | const HIST_WIDTH: f32 = 100.0; 125 | const BAR_WIDTH: f32 = 20.0; 126 | 127 | const RIGHT_PADDING: f32 = 100.0; 128 | let image_max_size = ( 129 | // Add right padding so that ticks and labels on the right fits 130 | window_size[0] - HIST_WIDTH - BAR_WIDTH - RIGHT_PADDING, 131 | window_size[1] - (cursor_pos[1] - window_pos[1]), 132 | ); 133 | let ([p, size], x_label_height) = state.show_image( 134 | self, 135 | texture_id, 136 | vunit, 137 | xaxis, 138 | yaxis, 139 | image_max_size, 140 | &mut *copying, 141 | &mut *store, 142 | &mut *attaching, 143 | outputid, 144 | &node_editor, 145 | )?; 146 | 147 | state.show_hist(self, [p[0] + size[0], p[1]], [HIST_WIDTH, size[1]]); 148 | let lut_bar_updated = state.show_bar( 149 | self, 150 | [p[0] + size[0] + HIST_WIDTH, p[1]], 151 | [BAR_WIDTH, size[1]], 152 | ); 153 | if lut_bar_updated { 154 | state 155 | .image() 156 | .update_texture(ctx, texture_id, textures, &state.lut)?; 157 | } 158 | 159 | self.set_cursor_screen_pos([p[0], p[1] + size[1] + x_label_height]); 160 | 161 | Ok(()) 162 | } 163 | 164 | fn color_image( 165 | &self, 166 | ctx: &F, 167 | textures: &mut Textures, 168 | texture_id: TextureId, 169 | vunit: &str, 170 | xaxis: Option<&AxisTransform>, 171 | yaxis: Option<&AxisTransform>, 172 | state: &mut State, 173 | copying: &mut Option<(InteractionId, TransformIdx)>, 174 | store: &mut EditabaleValues, 175 | attaching: &mut Option<(cake::OutputId, TransformIdx, usize)>, 176 | outputid: cake::OutputId, 177 | node_editor: &AflakNodeEditor, 178 | ) -> Result<(), Error> 179 | where 180 | F: Facade, 181 | FX: Fn(f32) -> f32, 182 | FY: Fn(f32) -> f32, 183 | I: Borrow>, 184 | { 185 | let window_pos = self.window_pos(); 186 | let cursor_pos = self.cursor_screen_pos(); 187 | let window_size = self.window_size(); 188 | const HIST_WIDTH: f32 = 100.0; 189 | const BAR_WIDTH: f32 = 20.0; 190 | 191 | const RIGHT_PADDING: f32 = 100.0; 192 | let image_max_size = ( 193 | // Add right padding so that ticks and labels on the right fits 194 | window_size[0] - HIST_WIDTH - BAR_WIDTH - RIGHT_PADDING, 195 | window_size[1] - (cursor_pos[1] - window_pos[1]), 196 | ); 197 | let ([p, size], x_label_height) = state.show_image( 198 | self, 199 | texture_id, 200 | vunit, 201 | xaxis, 202 | yaxis, 203 | image_max_size, 204 | &mut *copying, 205 | &mut *store, 206 | &mut *attaching, 207 | outputid, 208 | &node_editor, 209 | )?; 210 | 211 | state.show_hist_color(self, [p[0] + size[0], p[1]], [HIST_WIDTH, size[1]]); 212 | let lut_bar_updated = state.show_bar_rgb( 213 | self, 214 | [p[0] + size[0] + HIST_WIDTH, p[1]], 215 | [BAR_WIDTH, size[1]], 216 | ); 217 | if lut_bar_updated { 218 | state 219 | .image() 220 | .update_texture_color(ctx, texture_id, textures, &state.lut_color)?; 221 | } 222 | 223 | self.set_cursor_screen_pos([p[0], p[1] + size[1] + x_label_height]); 224 | 225 | Ok(()) 226 | } 227 | } 228 | 229 | /// Implementation of a UI to visualize a 2D image with ImGui and OpenGL. 230 | pub trait UiImage2d { 231 | fn image2d( 232 | &self, 233 | ctx: &F, 234 | textures: &mut Textures, 235 | texture_id: TextureId, 236 | vunit: &str, 237 | xaxis: Option<&AxisTransform>, 238 | yaxis: Option<&AxisTransform>, 239 | state: &mut State, 240 | copying: &mut Option<(InteractionId, TransformIdx)>, 241 | store: &mut EditabaleValues, 242 | attaching: &mut Option<(cake::OutputId, TransformIdx, usize)>, 243 | outputid: cake::OutputId, 244 | node_editor: &AflakNodeEditor, 245 | ) -> Result<(), Error> 246 | where 247 | F: Facade, 248 | FX: Fn(f32) -> f32, 249 | FY: Fn(f32) -> f32, 250 | I: Borrow>; 251 | 252 | fn color_image( 253 | &self, 254 | ctx: &F, 255 | textures: &mut Textures, 256 | texture_id: TextureId, 257 | vunit: &str, 258 | xaxis: Option<&AxisTransform>, 259 | yaxis: Option<&AxisTransform>, 260 | state: &mut State, 261 | copying: &mut Option<(InteractionId, TransformIdx)>, 262 | store: &mut EditabaleValues, 263 | attaching: &mut Option<(cake::OutputId, TransformIdx, usize)>, 264 | outputid: cake::OutputId, 265 | node_editor: &AflakNodeEditor, 266 | ) -> Result<(), Error> 267 | where 268 | F: Facade, 269 | FX: Fn(f32) -> f32, 270 | FY: Fn(f32) -> f32, 271 | I: Borrow>; 272 | } 273 | -------------------------------------------------------------------------------- /src/node_editor/examples/minimal.rs: -------------------------------------------------------------------------------- 1 | //! A minimal example that uses the node_editor crate 2 | #[macro_use] 3 | extern crate aflak_cake as cake; 4 | extern crate aflak_imgui_glium_support as support; 5 | extern crate imgui; 6 | #[macro_use] 7 | extern crate lazy_static; 8 | extern crate node_editor; 9 | extern crate serde; 10 | #[macro_use] 11 | extern crate serde_derive; 12 | extern crate variant_name; 13 | #[macro_use] 14 | extern crate variant_name_derive; 15 | 16 | use std::error::Error; 17 | use std::fmt; 18 | 19 | use imgui::{DrawListMut, Id, ImString, Ui}; 20 | use variant_name::VariantName; 21 | 22 | /// Values that we will transform in our node editor 23 | /// Must implement VariantName, so that we can retrieve a variant type by name. 24 | /// 25 | /// Our minimal editor will support integer and float! 26 | #[derive(Clone, VariantName, Serialize, Deserialize)] 27 | enum IOValue { 28 | Integer(i64), 29 | Float(f32), 30 | } 31 | 32 | /// Error type for failure during transformations 33 | #[derive(Debug)] 34 | struct IOErr; 35 | 36 | impl fmt::Display for IOErr { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | write!(f, "IOErr") 39 | } 40 | } 41 | 42 | impl Error for IOErr {} 43 | 44 | type MyNodeEditor = node_editor::NodeEditor; 45 | 46 | lazy_static! { 47 | /// Exhaustive list of all staticly loaded transforms that our node editor 48 | /// supports 49 | static ref TRANSFORMATIONS: Vec> = { 50 | vec![ 51 | cake_transform!( 52 | // description 53 | "A transformation that adds 1 to an integer", 54 | "Calculate", 55 | // version number 56 | 1, 0, 0, 57 | // definition 58 | plus1(i: Integer = 0) -> Integer { 59 | vec![Ok(IOValue::Integer(i+1))] 60 | } 61 | ), 62 | cake_transform!( 63 | "Very slow transformation that returns two floats", 64 | "Calculate", 65 | 1, 0, 0, 66 | slow(i: Integer = 0) -> Float, Float { 67 | use std::{thread, time}; 68 | // Do heavy work, i.e. sleep for 10 seconds 69 | thread::sleep(time::Duration::from_secs(10)); 70 | vec![ 71 | // Success for first ouput 72 | Ok(IOValue::Float(*i as f32)), 73 | // Failure for second output 74 | Err(IOErr), 75 | ] 76 | } 77 | ), 78 | ] 79 | }; 80 | } 81 | 82 | /// We need to define what kind of transformations can be applied to IOValue 83 | /// Each transformation has a name by which they are retrieved 84 | impl cake::NamedAlgorithms for IOValue { 85 | fn get_transform(s: &str) -> Option<&'static cake::Transform<'static, IOValue, IOErr>> { 86 | for t in TRANSFORMATIONS.iter() { 87 | if t.name() == s { 88 | return Some(t); 89 | } 90 | } 91 | None 92 | } 93 | } 94 | 95 | /// Define default values for each variant 96 | impl cake::DefaultFor for IOValue { 97 | fn default_for(variant_name: &str) -> Self { 98 | match variant_name { 99 | "Integer" => IOValue::Integer(0), 100 | "Float" => IOValue::Float(0.0), 101 | _ => panic!("Unknown variant name provided: {}.", variant_name), 102 | } 103 | } 104 | } 105 | 106 | /// Define list of editable variants (those variants must implement an imgui editor) 107 | impl cake::EditableVariants for IOValue { 108 | fn editable_variants() -> &'static [&'static str] { 109 | &["Integer", "Float"] 110 | } 111 | } 112 | 113 | fn integer_to_float(from: &IOValue) -> IOValue { 114 | if let IOValue::Integer(int) = from { 115 | IOValue::Float(*int as f32) 116 | } else { 117 | panic!("Unexpected input!") 118 | } 119 | } 120 | fn float_to_integer(from: &IOValue) -> IOValue { 121 | if let IOValue::Float(f) = from { 122 | IOValue::Integer(f.round() as _) 123 | } else { 124 | panic!("Unexpected input!") 125 | } 126 | } 127 | 128 | /// Define what type can be seamlessly casted into what 129 | impl cake::ConvertibleVariants for IOValue { 130 | const CONVERTION_TABLE: &'static [cake::ConvertibleVariant] = &[ 131 | cake::ConvertibleVariant { 132 | from: "Integer", 133 | into: "Float", 134 | f: integer_to_float, 135 | }, 136 | cake::ConvertibleVariant { 137 | from: "Float", 138 | into: "Integer", 139 | f: float_to_integer, 140 | }, 141 | ]; 142 | } 143 | 144 | /// Define an editor to edit each constant type 145 | #[derive(Default)] 146 | struct MyConstantEditor; 147 | 148 | impl node_editor::ConstantEditor for MyConstantEditor { 149 | fn editor<'a, I>( 150 | &self, 151 | ui: &Ui, 152 | constant: &IOValue, 153 | id: I, 154 | read_only: bool, 155 | _drawlist: &DrawListMut, 156 | ) -> Option 157 | where 158 | I: Into>, 159 | { 160 | let id_stack = ui.push_id(id); 161 | 162 | let mut some_new_value = None; 163 | 164 | ui.group(|| some_new_value = inner_editor(ui, constant, read_only)); 165 | 166 | if read_only && ui.is_item_hovered() { 167 | ui.tooltip_text("Read only, value is set by input!"); 168 | } 169 | 170 | id_stack.pop(); 171 | 172 | some_new_value 173 | } 174 | } 175 | 176 | fn inner_editor(ui: &Ui, constant: &IOValue, read_only: bool) -> Option { 177 | match *constant { 178 | IOValue::Integer(ref int) => { 179 | use std::i32; 180 | const MIN: i64 = i32::MIN as i64; 181 | const MAX: i64 = i32::MAX as i64; 182 | 183 | if MIN <= *int && *int <= MAX { 184 | let mut out = *int as i32; 185 | let changed = ui 186 | .input_int(format!("Int value"), &mut out) 187 | .read_only(read_only) 188 | .build(); 189 | if changed { 190 | Some(IOValue::Integer(i64::from(out))) 191 | } else { 192 | None 193 | } 194 | } else { 195 | ui.text(format!( 196 | "Cannot edit integer smaller than {}\nor bigger than {}!\nGot {}.", 197 | MIN, MAX, int 198 | )); 199 | None 200 | } 201 | } 202 | IOValue::Float(ref float) => { 203 | let mut f = *float; 204 | if ui 205 | .input_float(format!("Float value"), &mut f) 206 | .read_only(read_only) 207 | .build() 208 | { 209 | Some(IOValue::Float(f)) 210 | } else { 211 | None 212 | } 213 | } 214 | } 215 | } 216 | 217 | fn main() { 218 | let transformations_ref: Vec<_> = TRANSFORMATIONS.iter().collect(); 219 | 220 | let config = support::AppConfig { 221 | title: "Node editor example".to_owned(), 222 | ..Default::default() 223 | }; 224 | let mut editor = MyNodeEditor::default(); 225 | 226 | support::init(config).main_loop(move |ui, _, _| { 227 | let transformations = transformations_ref.as_slice(); 228 | 229 | imgui::Window::new(format!("Node editor")) 230 | .size([900.0, 600.0], imgui::Condition::FirstUseEver) 231 | .build(ui, || { 232 | // Render main editor 233 | editor.render(ui, transformations, &MyConstantEditor, &mut None); 234 | }); 235 | 236 | // Render macro editors and popups 237 | editor.inner_editors_render(ui, transformations, &MyConstantEditor, &mut None); 238 | editor.render_popups(ui); 239 | 240 | // Do something with outputs... For example, show them in a new imgui window 241 | let outputs = editor.outputs(); 242 | for (output, _) in outputs { 243 | let window_name = ImString::new(format!("Output #{}", output.id())); 244 | imgui::Window::new(&window_name) 245 | .size([400.0, 400.0], imgui::Condition::FirstUseEver) 246 | .build(ui, || { 247 | // Get result in each output 248 | let compute_state = editor.compute_output(output); 249 | 250 | // Show current state in a window 251 | match compute_state { 252 | None => { 253 | ui.text("Initialiazing..."); 254 | } 255 | Some(Err(e)) => { 256 | ui.text(format!("Error... {:?}", e)); 257 | } 258 | Some(Ok(result)) => { 259 | let value = cake::compute::SuccessOut::take(result); 260 | match &*value { 261 | IOValue::Integer(integer) => { 262 | ui.text(format!("{}", integer)); 263 | } 264 | IOValue::Float(float) => { 265 | ui.text(format!("{}", float)); 266 | } 267 | } 268 | } 269 | } 270 | }); 271 | } 272 | 273 | true 274 | }) 275 | } 276 | -------------------------------------------------------------------------------- /src/aflak_plot/src/imshow/zscale.rs: -------------------------------------------------------------------------------- 1 | /* 2 | This file is based on the part of STScI numdisplay package: 3 | https://github.com/spacetelescope/stsci.numdisplay/blob/b5062ec20673066b98d00e31fe165bb028db5181/lib/stsci/numdisplay/zscale.py 4 | 5 | under the following license: 6 | 7 | Copyright (C) 2005 Association of Universities for Research in Astronomy (AURA) 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above 16 | copyright notice, this list of conditions and the following 17 | disclaimer in the documentation and/or other materials provided 18 | with the distribution. 19 | 20 | 3. The name of AURA and its representatives may not be used to 21 | endorse or promote products derived from this software without 22 | specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 25 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 26 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 30 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 32 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 33 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 34 | DAMAGE. 35 | */ 36 | 37 | use super::image::{coerce_to_array_view2, Image}; 38 | use super::Error; 39 | use ndarray::{ArrayD, Axis}; 40 | use std::borrow::Borrow; 41 | 42 | impl Image 43 | where 44 | I: Borrow>, 45 | { 46 | pub fn zscale(&self, nsamples: i32, contrast: f32) -> (Option, Option) { 47 | const MIN_NPIXELS: i32 = 5; 48 | const MAX_REJECT: f32 = 0.5; 49 | const KREJ: f32 = 2.5; 50 | const MAX_ITERATIONS: i32 = 5; 51 | let mut sample = self.zsc_sample(nsamples).unwrap(); 52 | sample.sort_by(|a, b| a.partial_cmp(b).unwrap()); 53 | let npix = sample.len(); 54 | if let (Some(&zmin), Some(&zmax)) = (sample.first(), sample.last()) { 55 | let center_pixel = (npix - 1) / 2; 56 | let median; 57 | if npix % 2 == 1 { 58 | median = sample[center_pixel]; 59 | } else { 60 | median = 0.5 * (sample[center_pixel] + sample[center_pixel + 1]); 61 | } 62 | let minpix = MIN_NPIXELS.max((npix as f32 * MAX_REJECT) as i32); 63 | let ngrow = 1.max((npix as f32 * 0.01) as i32); 64 | let (ngoodpix, _zstart, mut zslope) = self 65 | .zsc_fit_line( 66 | sample, 67 | npix as i32, 68 | KREJ, 69 | ngrow, 70 | MAX_ITERATIONS, 71 | MIN_NPIXELS, 72 | MAX_REJECT, 73 | ) 74 | .unwrap(); 75 | let z1; 76 | let z2; 77 | if ngoodpix < minpix { 78 | z1 = zmin; 79 | z2 = zmax; 80 | } else { 81 | if contrast > 0.0 { 82 | zslope = zslope / contrast; 83 | } 84 | z1 = zmin.max(median - (center_pixel - 1) as f32 * zslope); 85 | z2 = zmax.min(median + (npix - center_pixel) as f32 * zslope); 86 | } 87 | (Some(z1), Some(z2)) 88 | } else { 89 | (None, None) 90 | } 91 | } 92 | fn zsc_sample(&self, maxpix: i32) -> Result, Error> { 93 | let ndim = self.ndim(); 94 | if ndim == 2 { 95 | let dim = self.dim(); 96 | let nc = dim.0; 97 | let nl = dim.1; 98 | let image_val = self.data(); 99 | let image_val = image_val.expect("Image is cached"); 100 | let image_val = coerce_to_array_view2(image_val).to_owned(); 101 | 102 | let stride = 1.max((((nc - 1) * (nl - 1)) as f32 / maxpix as f32).sqrt() as usize); 103 | let mut v = Vec::new(); 104 | for line in image_val.axis_iter(Axis(0)).step_by(stride) { 105 | for &data in line.iter().step_by(stride) { 106 | if !data.is_nan() { 107 | v.push(data); 108 | } 109 | } 110 | } 111 | v.truncate(maxpix as usize); 112 | Ok(v) 113 | } else { 114 | Err(Error::Msg("zsc sample failed.")) 115 | } 116 | } 117 | fn zsc_fit_line( 118 | &self, 119 | samples: Vec, 120 | npix: i32, 121 | krej: f32, 122 | ngrow: i32, 123 | maxiter: i32, 124 | min_npixels: i32, 125 | max_reject: f32, 126 | ) -> Result<(i32, f32, f32), Error> { 127 | let xscale = 2.0 / (npix - 1) as f32; 128 | let xnorm: Vec = (0..npix) 129 | .into_iter() 130 | .map(|v| v as f32 * xscale - 1.0) 131 | .collect(); 132 | let mut ngoodpix = npix; 133 | let minpix = min_npixels.max((npix as f32 * max_reject) as i32); 134 | let last_ngoodpix = npix + 1; 135 | let mut badpix = vec![0; npix as usize]; 136 | const GOOD_PIXEL: i32 = 0; 137 | let mut intercept = 0.0; 138 | let mut slope = 0.0; 139 | for _niter in 0..maxiter { 140 | if ngoodpix >= last_ngoodpix || ngoodpix < minpix { 141 | break; 142 | } 143 | let mut badpix_iter = badpix.iter(); 144 | let mut xnorm_cloned = xnorm.clone(); 145 | let mut samples_cloned = samples.clone(); 146 | xnorm_cloned.retain(|_| *badpix_iter.next().unwrap() == GOOD_PIXEL); 147 | let mut badpix_iter = badpix.iter(); 148 | samples_cloned.retain(|_| *badpix_iter.next().unwrap() == GOOD_PIXEL); 149 | let sum = xnorm_cloned.len(); 150 | let sumx: f32 = xnorm_cloned.iter().sum(); 151 | let sumxx: f32 = xnorm_cloned.iter().map(|x| x * x).sum(); 152 | let sumxy: f32 = xnorm_cloned 153 | .iter() 154 | .zip(samples_cloned.iter()) 155 | .map(|(x, y)| x * y) 156 | .sum(); 157 | let sumy: f32 = samples_cloned.iter().sum(); 158 | let delta = sum as f32 * sumxx - sumx * sumx; 159 | intercept = (sumxx * sumy - sumx * sumxy) / delta; 160 | slope = (sum as f32 * sumxy - sumx * sumy) / delta; 161 | 162 | let fitted: Vec = xnorm.iter().map(|x| x * slope + intercept).collect(); 163 | let flat: Vec = samples 164 | .iter() 165 | .zip(fitted.iter()) 166 | .map(|(x, y)| x - y) 167 | .collect(); 168 | let (ng, mean, sigma) = self 169 | .zsc_compute_sigma(flat.clone(), badpix.clone()) 170 | .unwrap(); 171 | ngoodpix = ng; 172 | if let (Some(_mean), Some(sigma)) = (mean, sigma) { 173 | let threshold = sigma * krej; 174 | let lcut = -threshold; 175 | let hcut = threshold; 176 | let next_badpix = badpix 177 | .iter() 178 | .zip(flat.iter()) 179 | .map(|(_, f)| if *f < lcut || *f > hcut { 1 } else { 0 }) 180 | .collect(); 181 | let kernel = vec![1, ngrow]; 182 | badpix = convolve(next_badpix, kernel); 183 | ngoodpix = badpix 184 | .iter() 185 | .filter(|&&x| x == GOOD_PIXEL) 186 | .cloned() 187 | .collect::>() 188 | .len() as i32; 189 | fn convolve(target: Vec, filter: Vec) -> Vec { 190 | let mut ret = Vec::new(); 191 | for head in 0..target.len() { 192 | let mut data = 0; 193 | for m in 0..filter.len() { 194 | if head < m { 195 | break; 196 | } 197 | data += target[head - m] * filter[m]; 198 | } 199 | ret.push(data); 200 | } 201 | ret 202 | } 203 | } 204 | } 205 | let zstart = intercept - slope; 206 | let zslope = slope * xscale; 207 | Ok((ngoodpix, zstart, zslope)) 208 | } 209 | 210 | fn zsc_compute_sigma( 211 | &self, 212 | mut flat: Vec, 213 | badpix: Vec, 214 | ) -> Result<(i32, Option, Option), Error> { 215 | const GOOD_PIXEL: i32 = 0; 216 | let mut badpix_iter = badpix.iter(); 217 | flat.retain(|_| *badpix_iter.next().unwrap() == GOOD_PIXEL); 218 | let sumz = flat.iter().sum(); 219 | let sumsq: f32 = flat.iter().map(|x| x * x).sum(); 220 | let mut badpix_cloned = badpix.clone(); 221 | badpix_cloned.retain(|v| *v == GOOD_PIXEL); 222 | let ngoodpix = badpix_cloned.len() as i32; 223 | let mean; 224 | let sigma; 225 | if ngoodpix == 0 { 226 | mean = None; 227 | sigma = None; 228 | } else if ngoodpix == 1 { 229 | mean = Some(sumz); 230 | sigma = None; 231 | } else { 232 | mean = Some(sumz / ngoodpix as f32); 233 | let temp: f32 = 234 | sumsq / (ngoodpix - 1) as f32 - sumz * sumz / (ngoodpix * (ngoodpix - 1)) as f32; 235 | if temp < 0.0 { 236 | sigma = Some(0.0); 237 | } else { 238 | sigma = Some(temp.sqrt()); 239 | } 240 | } 241 | Ok((ngoodpix, mean, sigma)) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/aflak_cake/src/dst/iterators.rs: -------------------------------------------------------------------------------- 1 | use std::collections::btree_map; 2 | use std::ops; 3 | use std::slice; 4 | use std::vec; 5 | 6 | use crate::dst::node::{Node, NodeId}; 7 | use crate::dst::MetaTransform; 8 | use crate::dst::{Input, InputList, InputSlot, Output, OutputId, TransformIdx, DST}; 9 | use crate::transform::Transform; 10 | use variant_name::VariantName; 11 | 12 | impl<'t, T: 't, E: 't> DST<'t, T, E> { 13 | pub(crate) fn transforms_iter(&self) -> TransformIterator<'_, 't, T, E> { 14 | TransformIterator::new(self.transforms.iter()) 15 | } 16 | 17 | pub(crate) fn meta_transforms_iter( 18 | &self, 19 | ) -> btree_map::Iter> { 20 | self.transforms.iter() 21 | } 22 | 23 | pub(crate) fn edges_iter(&self) -> EdgeIterator { 24 | EdgeIterator::new(self.edges.iter()) 25 | } 26 | 27 | /// Iterator over links. 28 | pub fn links_iter(&self) -> LinkIter { 29 | LinkIter::new(self.edges_iter(), self.outputs_iter()) 30 | } 31 | 32 | /// Iterator over outputs. 33 | pub fn outputs_iter(&self) -> btree_map::Iter, String)> { 34 | self.outputs.iter() 35 | } 36 | 37 | /// Iterator over nodes. 38 | pub fn nodes_iter(&self) -> NodeIter<'_, 't, T, E> { 39 | NodeIter { 40 | transforms: self.transforms_iter(), 41 | outputs: self.outputs_iter(), 42 | } 43 | } 44 | 45 | /// Return owned vector containing all [`NodeId`]s. 46 | pub fn node_ids(&self) -> Vec { 47 | self.nodes_iter().map(|(id, _)| id).collect() 48 | } 49 | 50 | pub(crate) fn _dependencies(&self, output: Output) -> DependencyIter<'_, 't, T, E> { 51 | DependencyIter { 52 | dst: self, 53 | stack: vec![output], 54 | completed_stack: vec![], 55 | } 56 | } 57 | } 58 | 59 | /// Make a post-order tree traversal to look for deepest dependencies first. 60 | /// Return the dependencies one at a time 61 | pub struct DependencyIter<'a, 't: 'a, T: 't, E: 't> { 62 | dst: &'a DST<'t, T, E>, 63 | stack: Vec, 64 | completed_stack: Vec, 65 | } 66 | 67 | pub struct Dependency { 68 | t_idx: TransformIdx, 69 | } 70 | 71 | impl Dependency { 72 | pub fn transform_idx(&self) -> TransformIdx { 73 | self.t_idx 74 | } 75 | } 76 | 77 | impl<'a, 't, T: 't, E> Iterator for DependencyIter<'a, 't, T, E> 78 | where 79 | T: VariantName, 80 | { 81 | type Item = Dependency; 82 | /// Push all parents on the stack recursively. 83 | /// If value has no parents, pop the stack and return it. 84 | fn next(&mut self) -> Option { 85 | if let Some(current_output) = self.stack.pop() { 86 | if let Some(mut parent_outputs) = 87 | self.dst.outputs_attached_to_transform(current_output.t_idx) 88 | { 89 | let dep = Dependency { 90 | t_idx: current_output.t_idx, 91 | }; 92 | if parent_outputs.is_empty() { 93 | Some(dep) 94 | } else { 95 | parent_outputs.retain(Option::is_some); 96 | self.stack.extend( 97 | parent_outputs 98 | .into_iter() 99 | .map(Option::unwrap) 100 | .collect::>(), 101 | ); 102 | self.completed_stack.push(dep); 103 | self.next() 104 | } 105 | } else { 106 | self.completed_stack.pop() 107 | } 108 | } else { 109 | self.completed_stack.pop() 110 | } 111 | } 112 | } 113 | 114 | pub struct EdgeIterator<'a> { 115 | edges: btree_map::Iter<'a, Output, InputList>, 116 | output: Option<&'a Output>, 117 | inputs: slice::Iter<'a, Input>, 118 | } 119 | 120 | impl<'a> EdgeIterator<'a> { 121 | fn new(edges: btree_map::Iter<'a, Output, InputList>) -> Self { 122 | const NO_INPUT: [Input; 0] = []; 123 | Self { 124 | edges, 125 | output: None, 126 | inputs: NO_INPUT.iter(), 127 | } 128 | } 129 | } 130 | 131 | impl<'a> Iterator for EdgeIterator<'a> { 132 | type Item = (&'a Output, &'a Input); 133 | fn next(&mut self) -> Option { 134 | if let Some(input) = self.inputs.next() { 135 | Some((self.output.unwrap(), input)) 136 | } else if let Some((output, input_list)) = self.edges.next() { 137 | self.output = Some(output); 138 | self.inputs = input_list.inputs.iter(); 139 | self.next() 140 | } else { 141 | None 142 | } 143 | } 144 | } 145 | 146 | pub struct TransformIterator<'a, 't: 'a, T: 't, E: 't> { 147 | iter: btree_map::Iter<'a, TransformIdx, MetaTransform<'t, T, E>>, 148 | } 149 | impl<'a, 't, T, E> TransformIterator<'a, 't, T, E> { 150 | fn new(iter: btree_map::Iter<'a, TransformIdx, MetaTransform<'t, T, E>>) -> Self { 151 | Self { iter } 152 | } 153 | } 154 | 155 | impl<'a, 't, T, E> Iterator for TransformIterator<'a, 't, T, E> { 156 | type Item = (&'a TransformIdx, &'a Transform<'t, T, E>); 157 | fn next(&mut self) -> Option { 158 | self.iter.next().map(|(idx, t)| (idx, t.transform())) 159 | } 160 | } 161 | 162 | /// Iterator over nodes. 163 | /// 164 | /// Iterate over a tuple ([`NodeId`], [`Node`]). 165 | pub struct NodeIter<'a, 't: 'a, T: 't, E: 't> { 166 | transforms: TransformIterator<'a, 't, T, E>, 167 | outputs: btree_map::Iter<'a, OutputId, (Option, String)>, 168 | } 169 | 170 | /// Iterate over nodes. 171 | impl<'a, 't, T, E> Iterator for NodeIter<'a, 't, T, E> { 172 | type Item = (NodeId, Node<'a, 't, T, E>); 173 | fn next(&mut self) -> Option { 174 | if let Some((id, t)) = self.transforms.next() { 175 | Some((NodeId::Transform(*id), Node::Transform(t))) 176 | } else if let Some((id, (o, name))) = self.outputs.next() { 177 | Some(( 178 | NodeId::Output(*id), 179 | Node::Output((o.as_ref(), name.clone())), 180 | )) 181 | } else { 182 | None 183 | } 184 | } 185 | } 186 | 187 | /// Iterator over links. 188 | /// 189 | /// A link is a tuple ([`Output`], [`InputSlot`]). It is attached on one side to 190 | /// the [`Output`] of a transformation and to the other side on an input slot. 191 | /// The input slot is either the input to another [`Transform`] or the 192 | /// input slot of an output node. 193 | pub struct LinkIter<'a> { 194 | edges: EdgeIterator<'a>, 195 | outputs: btree_map::Iter<'a, OutputId, (Option, String)>, 196 | } 197 | 198 | impl<'a> LinkIter<'a> { 199 | fn new( 200 | edges: EdgeIterator<'a>, 201 | outputs: btree_map::Iter<'a, OutputId, (Option, String)>, 202 | ) -> Self { 203 | Self { edges, outputs } 204 | } 205 | } 206 | 207 | /// Iterate over links. 208 | impl<'a> Iterator for LinkIter<'a> { 209 | type Item = (&'a Output, InputSlot); 210 | fn next(&mut self) -> Option { 211 | if let Some((output, input)) = self.edges.next() { 212 | Some((output, InputSlot::Transform(*input))) 213 | } else if let Some((output_id, (output, _))) = self.outputs.next() { 214 | if let Some(output) = output { 215 | Some((output, InputSlot::Output(*output_id))) 216 | } else { 217 | self.next() 218 | } 219 | } else { 220 | None 221 | } 222 | } 223 | } 224 | 225 | impl<'t, T, E> DST<'t, T, E> 226 | where 227 | T: Clone, 228 | { 229 | pub(crate) fn unattached_input_slots(&self) -> impl Iterator + '_ { 230 | let output_ids = self.unattached_output_ids().map(InputSlot::Output); 231 | let inputs = self 232 | .unattached_inputs() 233 | .into_iter() 234 | .map(InputSlot::Transform); 235 | output_ids.chain(inputs) 236 | } 237 | 238 | fn unattached_output_ids(&self) -> impl Iterator + '_ { 239 | self.outputs 240 | .iter() 241 | .filter(|(_, (some_output, _))| some_output.is_none()) 242 | .map(|(output_id, _)| *output_id) 243 | } 244 | 245 | fn unattached_inputs(&self) -> Vec { 246 | self.transform_inputs() 247 | .filter(|input| { 248 | for input_list in self.edges.values() { 249 | if input_list.contains(input) { 250 | return false; 251 | } 252 | } 253 | true 254 | }) 255 | .collect() 256 | } 257 | 258 | fn transform_inputs(&self) -> impl Iterator + '_ { 259 | TransformInputIterator::new(self) 260 | } 261 | } 262 | 263 | struct TransformInputIterator { 264 | inner: vec::IntoIter<(TransformIdx, usize)>, 265 | cursor: Option, 266 | } 267 | 268 | struct TransformInputCursor { 269 | t_idx: TransformIdx, 270 | inputs: ops::Range, 271 | } 272 | 273 | impl TransformInputCursor { 274 | fn next(&mut self) -> Option { 275 | self.inputs 276 | .next() 277 | .map(|index| Input::new(self.t_idx, index)) 278 | } 279 | } 280 | 281 | impl TransformInputIterator { 282 | fn new(dst: &DST<'_, T, E>) -> Self 283 | where 284 | T: Clone, 285 | { 286 | Self { 287 | inner: dst 288 | .transforms 289 | .iter() 290 | .map(|(t_idx, meta)| (*t_idx, meta.transform().inputs().len())) 291 | .collect::>() 292 | .into_iter(), 293 | cursor: None, 294 | } 295 | } 296 | } 297 | 298 | impl Iterator for TransformInputIterator { 299 | type Item = Input; 300 | fn next(&mut self) -> Option { 301 | if self.cursor.is_none() { 302 | if let Some((t_idx, len)) = self.inner.next() { 303 | self.cursor = Some(TransformInputCursor { 304 | t_idx, 305 | inputs: 0..len, 306 | }); 307 | } else { 308 | return None; 309 | } 310 | } 311 | 312 | let mut exhausted = false; 313 | if let Some(ref mut cursor) = &mut self.cursor { 314 | if let Some(input) = cursor.next() { 315 | return Some(input); 316 | } else { 317 | exhausted = true; 318 | } 319 | } 320 | 321 | if exhausted { 322 | self.cursor = None; 323 | } 324 | self.next() 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/imgui_tone_curve/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate imgui; 2 | extern crate serde; 3 | extern crate serde_derive; 4 | 5 | use imgui::*; 6 | use serde::{Deserialize, Serialize}; 7 | use std::io; 8 | 9 | #[derive(Clone, Debug, Serialize, Deserialize)] 10 | pub struct ToneCurveState { 11 | arr: Vec, 12 | cp: Vec<[f32; 2]>, 13 | adding: Option<[f32; 2]>, 14 | pushed: bool, 15 | moving: Option, 16 | deleting: Option, 17 | x_clicking: usize, 18 | is_dragging: bool, 19 | } 20 | 21 | impl ToneCurveState { 22 | pub fn default() -> Self { 23 | ToneCurveState { 24 | arr: { 25 | let mut vec = vec![]; 26 | for i in 0..256 { 27 | vec.push(i as f32); 28 | } 29 | vec 30 | }, 31 | cp: vec![[0.0, 0.0], [1.0, 1.0]], 32 | adding: None, 33 | pushed: false, 34 | moving: None, 35 | deleting: None, 36 | x_clicking: 0, 37 | is_dragging: false, 38 | } 39 | } 40 | 41 | pub fn control_points(&self) -> &Vec<[f32; 2]> { 42 | &self.cp 43 | } 44 | 45 | pub fn array(&self) -> &Vec { 46 | &self.arr 47 | } 48 | } 49 | 50 | impl PartialEq for ToneCurveState { 51 | fn eq(&self, val: &Self) -> bool { 52 | let cp1 = self.control_points(); 53 | let cp2 = val.control_points(); 54 | cp1 == cp2 && self.deleting == val.deleting 55 | } 56 | } 57 | 58 | pub trait UiToneCurve { 59 | fn create_curve(cp: &Vec<[f32; 2]>) -> Vec; 60 | fn tone_curve( 61 | &self, 62 | state: &mut ToneCurveState, 63 | draw_list: &DrawListMut, 64 | ) -> io::Result>; 65 | } 66 | 67 | impl<'ui> UiToneCurve for Ui<'ui> { 68 | fn create_curve(cp: &Vec<[f32; 2]>) -> Vec { 69 | let mut ret = Vec::new(); 70 | match cp.len() { 71 | 0 | 1 => { 72 | vec![] 73 | } 74 | 2 => { 75 | let slope = (cp[1][1] - cp[0][1]) / (cp[1][0] - cp[0][0]); 76 | let ys = cp[0][1]; 77 | for i in 0..256 { 78 | let t = i as f32 / 256.0; 79 | ret.push((t * slope + ys) * 256.0); 80 | } 81 | ret 82 | } 83 | 3 => { 84 | let mut cp = cp.clone(); 85 | cp.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap()); 86 | let (x1, x2, x3, y1, y2, y3) = 87 | (cp[0][0], cp[1][0], cp[2][0], cp[0][1], cp[1][1], cp[2][1]); 88 | let a = ((y1 - y2) * (x1 - x3) - (y1 - y3) * (x1 - x2)) 89 | / ((x1 - x2) * (x1 - x3) * (x2 - x3)); 90 | let b = (y1 - y2) / (x1 - x2) - a * (x1 + x2); 91 | let c = y1 - a * x1 * x1 - b * x1; 92 | for i in 0..256 { 93 | let t = i as f32 / 256.0; 94 | ret.push((a * t * t + b * t + c) * 256.0); 95 | } 96 | ret 97 | } 98 | _ => { 99 | let mut cp = cp.clone(); 100 | cp.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap()); 101 | let mut counter = 0; 102 | for _ in 0..cp.len() - 1 { 103 | if counter == 0 { 104 | let p0 = cp[0]; 105 | let p1 = cp[1]; 106 | let p2 = cp[2]; 107 | let size = (p1[0] * 256.0).ceil() as usize; 108 | let b = [p0[0] - 2.0 * p1[0] + p2[0], p0[1] - 2.0 * p1[1] + p2[1]]; 109 | let c = [ 110 | -3.0 * p0[0] + 4.0 * p1[0] - p2[0], 111 | -3.0 * p0[1] + 4.0 * p1[1] - p2[1], 112 | ]; 113 | let d = [2.0 * p0[0], 2.0 * p0[1]]; 114 | for i in 0..size { 115 | let t = i as f32 / (size - 1) as f32; 116 | ret.push(((b[1] * t * t) + (c[1] * t) + d[1]) * 128.0); 117 | } 118 | } else if counter == cp.len() - 2 { 119 | let p0 = cp[counter - 1]; 120 | let p1 = cp[counter]; 121 | let p2 = cp[counter + 1]; 122 | let size = ((p2[0] * 256.0).floor() - (p1[0] * 256.0).ceil()) as usize; 123 | let b = [p0[0] - 2.0 * p1[0] + p2[0], p0[1] - 2.0 * p1[1] + p2[1]]; 124 | let c = [-p0[0] + p2[0], -p0[1] + p2[1]]; 125 | let d = [2.0 * p1[0], 2.0 * p1[1]]; 126 | for i in 0..size { 127 | let t = i as f32 / (size - 1) as f32; 128 | ret.push(((b[1] * t * t) + (c[1] * t) + d[1]) * 128.0); 129 | } 130 | } else { 131 | let p0 = cp[counter - 1]; 132 | let p1 = cp[counter]; 133 | let p2 = cp[counter + 1]; 134 | let p3 = cp[counter + 2]; 135 | let size = ((p2[0] * 256.0).floor() - (p1[0] * 256.0).ceil()) as usize + 1; 136 | let a = [ 137 | -p0[0] + 3.0 * p1[0] - 3.0 * p2[0] + p3[0], 138 | -p0[1] + 3.0 * p1[1] - 3.0 * p2[1] + p3[1], 139 | ]; 140 | let b = [ 141 | 2.0 * p0[0] - 5.0 * p1[0] + 4.0 * p2[0] - p3[0], 142 | 2.0 * p0[1] - 5.0 * p1[1] + 4.0 * p2[1] - p3[1], 143 | ]; 144 | let c = [-p0[0] + p2[0], -p0[1] + p2[1]]; 145 | let d = [2.0 * p1[0], 2.0 * p1[1]]; 146 | for i in 0..size { 147 | let t = i as f32 / (size - 1) as f32; 148 | ret.push( 149 | ((a[1] * t * t * t) + (b[1] * t * t) + (c[1] * t) + d[1]) * 128.0, 150 | ); 151 | } 152 | } 153 | counter += 1; 154 | } 155 | ret 156 | } 157 | } 158 | } 159 | 160 | fn tone_curve( 161 | &self, 162 | state: &mut ToneCurveState, 163 | draw_list: &DrawListMut, 164 | ) -> io::Result> { 165 | let p = self.cursor_screen_pos(); 166 | let mouse_pos = self.io().mouse_pos; 167 | let [mouse_x, mouse_y] = [mouse_pos[0] - p[0] - 5.0, mouse_pos[1] - p[1] - 5.0]; 168 | state.arr = Self::create_curve(&state.cp); 169 | self.invisible_button(format!("tone_curve"), [410.0, 410.0]); 170 | self.set_cursor_screen_pos(p); 171 | PlotLines::new(self, format!(""), &state.arr) 172 | .graph_size([410.0, 410.0]) 173 | .scale_min(0.0) 174 | .scale_max(256.0) 175 | .build(); 176 | self.set_cursor_screen_pos(p); 177 | self.invisible_button(format!("tone_curve"), [410.0, 410.0]); 178 | if let Some(adding) = state.adding { 179 | let x = adding[0] * 400.0 + 5.0 + p[0]; 180 | let y = (1.0 - adding[1]) * 400.0 + 5.0 + p[1]; 181 | draw_list.add_circle([x, y], 5.0, 0xFF00_FFFF).build(); 182 | } 183 | let mut counter = 0; 184 | for i in &state.cp { 185 | let x = i[0] * 400.0 + 5.0 + p[0]; 186 | let y = (1.0 - i[1]) * 400.0 + 5.0 + p[1]; 187 | if (x - mouse_pos[0]) * (x - mouse_pos[0]) + (y - mouse_pos[1]) * (y - mouse_pos[1]) 188 | < 25.0 189 | { 190 | draw_list.add_circle([x, y], 5.0, 0xFF00_00FF).build(); 191 | if self.is_mouse_clicked(MouseButton::Left) && state.adding == None { 192 | state.moving = Some(counter); 193 | } 194 | if self.is_mouse_clicked(MouseButton::Right) { 195 | self.open_popup(format!("delete-control-point")); 196 | state.deleting = Some(counter); 197 | } 198 | } else { 199 | draw_list.add_circle([x, y], 5.0, 0xFFFF_FFFF).build(); 200 | } 201 | counter += 1; 202 | } 203 | self.popup(format!("delete-control-point"), || { 204 | if MenuItem::new(format!("Delete Control Point")).build(self) { 205 | if let Some(key) = state.deleting { 206 | state.cp.remove(key); 207 | state.deleting = None; 208 | } 209 | } 210 | }); 211 | if self.is_item_hovered() { 212 | if self.is_mouse_clicked(MouseButton::Left) && state.moving == None { 213 | if !state.is_dragging { 214 | //let x = 256.0 * mouse_x / 400.0; 215 | //state.x_clicking = x as usize; 216 | state.is_dragging = true; 217 | } 218 | } 219 | if state.is_dragging { 220 | if state.pushed == false { 221 | state.pushed = true; 222 | state.cp.push([mouse_x / 400.0, (400.0 - mouse_y) / 400.0]); 223 | } 224 | state.adding = Some([mouse_x / 400.0, (400.0 - mouse_y) / 400.0]); 225 | let lastidx = state.cp.len() - 1; 226 | state.cp[lastidx] = state.adding.unwrap(); 227 | if state.x_clicking > 255 { 228 | state.x_clicking = 255; 229 | } 230 | //state.arr[state.x_clicking] = 256.0 * (400.0 - mouse_y) / 400.0; 231 | /*if state.x_clicking == 255 { 232 | let lastidx = state.cp.len() - 1; 233 | state.cp[lastidx] = [1.0, (400.0 - mouse_y) / 400.0]; 234 | } else if state.x_clicking == 0 { 235 | state.cp[0] = [0.0, (400.0 - mouse_y) / 400.0]; 236 | } else { 237 | 238 | }*/ 239 | if !self.is_mouse_down(MouseButton::Left) { 240 | state.cp.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap()); 241 | state.adding = None; 242 | state.is_dragging = false; 243 | state.pushed = false; 244 | } 245 | /*if !self.is_mouse_down(MouseButton::Left) { 246 | println!("{}", state.x_clicking); 247 | state.cp.push([mouse_x / 400.0, (400.0 - mouse_y) / 400.0]); 248 | state.cp.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap()); 249 | state.is_dragging = false; 250 | state.x_clicking = 0; 251 | }*/ 252 | } 253 | if let Some(key) = state.moving { 254 | state.cp[key] = [mouse_x / 400.0, (400.0 - mouse_y) / 400.0]; 255 | if !self.is_mouse_down(MouseButton::Left) { 256 | state.moving = None; 257 | } 258 | } 259 | } 260 | let state = state.clone(); 261 | Ok(Some(state)) 262 | } 263 | } 264 | --------------------------------------------------------------------------------