WIP: image processing library (or libraries?) #12
4 changed files with 356 additions and 3 deletions
|
@ -56,17 +56,20 @@ fn main() {
|
||||||
|
|
||||||
mod dev {
|
mod dev {
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use prowocessing::experimental::enum_based::{Pipeline, PipelineBuilder};
|
use prowocessing::experimental::trait_based::DataType;
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub(crate) enum DevCommands {
|
pub(crate) enum DevCommands {
|
||||||
Enums { test_str: String },
|
Enums { test_str: String },
|
||||||
|
Add { num0: i32, num1: i32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DevCommands {
|
impl DevCommands {
|
||||||
pub fn run(self) {
|
pub fn run(self) {
|
||||||
match self {
|
match self {
|
||||||
DevCommands::Enums { test_str } => {
|
DevCommands::Enums { test_str } => {
|
||||||
|
use prowocessing::experimental::enum_based::PipelineBuilder;
|
||||||
|
|
||||||
let upr = PipelineBuilder::new()
|
let upr = PipelineBuilder::new()
|
||||||
.insert(prowocessing::experimental::enum_based::Instruction::Uppercase)
|
.insert(prowocessing::experimental::enum_based::Instruction::Uppercase)
|
||||||
.build();
|
.build();
|
||||||
|
@ -77,6 +80,15 @@ mod dev {
|
||||||
println!("Upr: {}", upr.run(test_str.clone()));
|
println!("Upr: {}", upr.run(test_str.clone()));
|
||||||
println!("Lwr: {}", lwr.run(test_str.clone()));
|
println!("Lwr: {}", lwr.run(test_str.clone()));
|
||||||
}
|
}
|
||||||
|
DevCommands::Add { num0, num1 } => {
|
||||||
|
use prowocessing::experimental::trait_based::PipelineBuilder;
|
||||||
|
|
||||||
|
let pipe = PipelineBuilder::new().add(1).stringify().build();
|
||||||
|
println!(
|
||||||
|
"{:?}",
|
||||||
multisamplednight marked this conversation as resolved
Outdated
|
|||||||
|
pipe.run(vec![num0.into(), num1.into()].into()).into_inner()[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
pub mod enum_based;
|
pub mod enum_based;
|
||||||
|
|
||||||
|
pub mod trait_based;
|
||||||
|
|
339
crates/prowocessing/src/experimental/trait_based.rs
Normal file
339
crates/prowocessing/src/experimental/trait_based.rs
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
use self::{
|
||||||
|
numops::{Add, Stringify, Subtract},
|
||||||
|
strops::{Concatenate, Lower, Upper},
|
||||||
|
};
|
||||||
|
|
||||||
|
trait PipelineElement {
|
||||||
|
fn runner(&self) -> fn(&Inputs) -> Outputs;
|
||||||
|
fn signature(&self) -> ElementIo;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ElementIo {
|
||||||
|
pub inputs: Vec<DataType>,
|
||||||
|
pub outputs: Vec<DataType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Bind additional inputs if instruction has more then one and is passd without any additional
|
||||||
|
// - allow binding to pointers to other pipelines?
|
||||||
|
// - allow referencing earlier data
|
||||||
|
pub struct PipelineBuilder {
|
||||||
|
elements: Vec<Box<dyn PipelineElement>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pipeline {
|
||||||
|
runners: Vec<fn(&Inputs) -> Outputs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn run(&self, inputs: Inputs) -> Outputs {
|
||||||
|
let mut out: Outputs = inputs.into();
|
||||||
|
|
||||||
|
for runner in &self.runners {
|
||||||
|
out = runner(&(&out).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PipelineBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
elements: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert<T: PipelineElement + 'static>(mut self, el: T) -> Self {
|
||||||
|
if let Some(previous_item) = self.elements.last() {
|
||||||
|
assert_eq!(
|
||||||
|
previous_item.signature().outputs[0],
|
||||||
|
el.signature().inputs[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.elements.push(Box::new(el));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn concatenate(self, sec: String) -> Self {
|
||||||
|
self.insert(Concatenate(sec))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn upper(self) -> Self {
|
||||||
|
self.insert(Upper)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn lower(self) -> Self {
|
||||||
|
self.insert(Lower)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(
|
||||||
|
clippy::should_implement_trait,
|
||||||
|
reason = "is not equivalent to addition"
|
||||||
|
)]
|
||||||
|
pub fn add(self, sec: i32) -> Self {
|
||||||
|
self.insert(Add(sec))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn subtract(self, sec: i32) -> Self {
|
||||||
|
self.insert(Subtract(sec))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn stringify(self) -> Self {
|
||||||
|
self.insert(Stringify)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Pipeline {
|
||||||
|
let mut r = Vec::new();
|
||||||
|
|
||||||
|
self.elements.iter().for_each(|el| r.push(el.runner()));
|
||||||
|
|
||||||
|
Pipeline { runners: r }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PipelineBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Data<'a> {
|
||||||
|
String(&'a str),
|
||||||
|
Int(i32),
|
||||||
|
}
|
||||||
|
impl Data<'_> {
|
||||||
|
pub fn to_owned_data(&self) -> OwnedData {
|
||||||
|
match self {
|
||||||
|
Data::String(s) => (*s).to_owned().into(),
|
||||||
|
Data::Int(i) => (*i).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> From<&'a str> for Data<'a> {
|
||||||
|
fn from(value: &'a str) -> Self {
|
||||||
|
Self::String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<i32> for Data<'_> {
|
||||||
|
fn from(value: i32) -> Self {
|
||||||
|
Self::Int(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> From<&'a OwnedData> for Data<'a> {
|
||||||
|
fn from(value: &'a OwnedData) -> Self {
|
||||||
|
match value {
|
||||||
|
OwnedData::String(s) => Data::String(s),
|
||||||
|
OwnedData::Int(i) => Data::Int(*i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum OwnedData {
|
||||||
|
String(String),
|
||||||
|
Int(i32),
|
||||||
|
}
|
||||||
|
impl From<String> for OwnedData {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self::String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<i32> for OwnedData {
|
||||||
|
fn from(value: i32) -> Self {
|
||||||
|
Self::Int(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum DataType {
|
||||||
|
String,
|
||||||
|
Int,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Inputs<'a>(Vec<Data<'a>>);
|
||||||
|
impl<'a> Inputs<'a> {
|
||||||
|
fn inner(&self) -> Vec<Data<'a>> {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> From<Vec<Data<'a>>> for Inputs<'a> {
|
||||||
|
fn from(value: Vec<Data<'a>>) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Into<Data<'a>>> From<T> for Inputs<'a> {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self(vec![value.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> From<&'a Outputs> for Inputs<'a> {
|
||||||
|
fn from(value: &'a Outputs) -> Self {
|
||||||
|
Self(value.0.iter().map(std::convert::Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Outputs(Vec<OwnedData>);
|
||||||
|
impl Outputs {
|
||||||
|
pub fn into_inner(self) -> Vec<OwnedData> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Vec<OwnedData>> for Outputs {
|
||||||
|
fn from(value: Vec<OwnedData>) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Into<OwnedData>> From<T> for Outputs {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self(vec![value.into()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Inputs<'_>> for Outputs {
|
||||||
|
fn from(value: Inputs) -> Self {
|
||||||
|
Self(
|
||||||
|
value
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.map(|i: Data<'_>| Data::to_owned_data(&i))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod strops {
|
||||||
|
use super::{Data, DataType, ElementIo, Inputs, Outputs, PipelineElement};
|
||||||
|
|
||||||
|
pub struct Concatenate(pub String);
|
||||||
|
impl PipelineElement for Concatenate {
|
||||||
|
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
||||||
|
|input| {
|
||||||
|
if let [Data::String(s0), Data::String(s1), ..] = input.inner()[..] {
|
||||||
|
format!("{s0}{s1}").into()
|
||||||
|
} else {
|
||||||
|
panic!("Invalid data passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> ElementIo {
|
||||||
|
ElementIo {
|
||||||
|
inputs: vec![DataType::String, DataType::String],
|
||||||
|
outputs: vec![DataType::String],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Upper;
|
||||||
|
impl PipelineElement for Upper {
|
||||||
|
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
||||||
|
|input| {
|
||||||
|
if let [Data::String(s), ..] = input.inner()[..] {
|
||||||
|
s.to_uppercase().into()
|
||||||
|
} else {
|
||||||
|
panic!("Invalid data passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> ElementIo {
|
||||||
|
ElementIo {
|
||||||
|
inputs: vec![DataType::String],
|
||||||
|
outputs: vec![DataType::String],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Lower;
|
||||||
|
impl PipelineElement for Lower {
|
||||||
|
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
||||||
|
|input| {
|
||||||
|
if let [Data::String(s), ..] = input.inner()[..] {
|
||||||
|
s.to_lowercase().into()
|
||||||
|
} else {
|
||||||
|
panic!("Invalid data passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> ElementIo {
|
||||||
|
ElementIo {
|
||||||
|
inputs: vec![DataType::String],
|
||||||
|
outputs: vec![DataType::String],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod numops {
|
||||||
|
use core::panic;
|
||||||
|
|
||||||
|
use super::{Data, DataType, ElementIo, Inputs, Outputs, PipelineElement};
|
||||||
|
|
||||||
|
pub struct Add(pub i32);
|
||||||
|
impl PipelineElement for Add {
|
||||||
|
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
||||||
|
|input| {
|
||||||
|
if let [Data::Int(i0), Data::Int(i1), ..] = input.inner()[..] {
|
||||||
|
(i0 + i1).into()
|
||||||
|
} else {
|
||||||
|
panic!("Invalid data passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> ElementIo {
|
||||||
|
ElementIo {
|
||||||
|
inputs: vec![DataType::Int, DataType::Int],
|
||||||
|
outputs: vec![DataType::Int],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Subtract(pub i32);
|
||||||
|
impl PipelineElement for Subtract {
|
||||||
|
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
||||||
|
|input| {
|
||||||
|
if let [Data::Int(i0), Data::Int(i1), ..] = input.inner()[..] {
|
||||||
|
(i0 + i1).into()
|
||||||
|
} else {
|
||||||
|
panic!("Invalid data passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> ElementIo {
|
||||||
|
ElementIo {
|
||||||
|
inputs: vec![DataType::Int, DataType::Int],
|
||||||
|
outputs: vec![DataType::Int],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stringify;
|
||||||
|
impl PipelineElement for Stringify {
|
||||||
|
fn runner(&self) -> fn(&Inputs) -> Outputs {
|
||||||
|
|input| {
|
||||||
|
if let [Data::Int(int), ..] = input.inner()[..] {
|
||||||
|
int.to_string().into()
|
||||||
|
} else {
|
||||||
|
panic!("Invalid data passed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> ElementIo {
|
||||||
|
ElementIo {
|
||||||
|
inputs: vec![DataType::Int],
|
||||||
|
outputs: vec![DataType::String],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! One of the design goals for this library is, however, to be a simple, generic image processing library.
|
//! One of the design goals for this library is, however, to be a simple, generic image processing library.
|
||||||
//! For now, it's just indev... lets see what comes of it!
|
//! For now, it's just indev... lets see what comes of it!
|
||||||
|
#![feature(lint_reasons)]
|
||||||
use experimental::enum_based::{Instruction, PipelineBuilder};
|
|
||||||
|
|
||||||
/// just some experiments, to test whether the architecture i want is even possible (or how to do it). probably temporary.
|
/// just some experiments, to test whether the architecture i want is even possible (or how to do it). probably temporary.
|
||||||
/// Gonna first try string processing...
|
/// Gonna first try string processing...
|
||||||
|
@ -11,6 +10,7 @@ pub mod experimental;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_enums() {
|
fn test_enums() {
|
||||||
|
use crate::experimental::enum_based::{Instruction, PipelineBuilder};
|
||||||
let builder = PipelineBuilder::new().insert(Instruction::Uppercase);
|
let builder = PipelineBuilder::new().insert(Instruction::Uppercase);
|
||||||
let upr = builder.build();
|
let upr = builder.build();
|
||||||
let upr_lowr = builder.insert(Instruction::Lowercase).build();
|
let upr_lowr = builder.insert(Instruction::Lowercase).build();
|
||||||
|
|
Loading…
Reference in a new issue
All of these dev commands seem like they'd belong into tests using
#[test]
instead.that's what i already mentioned, that i prefer playing around like that
sure, can do that, but i much prefer having them run like that and output there so i can see it more or less interactively.
planning to do that, but the
experimental
things are all termporary anyway.They're still just as interactive if one were to use
cargo test --no-capture <test_name>
.They're what this PR introduces and what you requested my review on.
They are indeed not what this PR introduces, those will not be in the PR once it's complete. this is basically just a working copy/scratchpad state for now, and i'll be purging it once the actual library is done which is then what will be introduced by this PR.
Fair enough.