lib migration pt3
This commit is contained in:
parent
7d35959ac2
commit
4d82ea5567
|
@ -0,0 +1,62 @@
|
|||
// use crate::components::states::MainState;
|
||||
use crate::transfer::Transfer;
|
||||
use crate::util::*;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TransferRecord {
|
||||
#[serde(rename = "Source Plate", alias = "source plate", alias = "src plate")]
|
||||
pub source_plate: String,
|
||||
#[serde(rename = "Source Well", alias = "source well", alias = "src well")]
|
||||
pub source_well: String,
|
||||
#[serde(rename = "Dest Plate", alias = "dest plate", alias = "destination plate")]
|
||||
pub destination_plate: String,
|
||||
#[serde(rename = "Destination Well", alias = "destination well", alias = "dest well")]
|
||||
pub destination_well: String,
|
||||
#[serde(rename = "Transfer Volume", alias = "transfer volume")]
|
||||
pub volume: f32,
|
||||
#[serde(rename = "Concentration", alias = "concentration")]
|
||||
pub concentration: Option<f32>,
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
pub fn transfer_to_records(
|
||||
tr: &Transfer,
|
||||
src_barcode: &str,
|
||||
dest_barcode: &str,
|
||||
) -> Vec<TransferRecord> {
|
||||
let source_wells = tr.transfer_region.get_source_wells();
|
||||
let map = tr.transfer_region.calculate_map();
|
||||
|
||||
let mut records: Vec<TransferRecord> = vec![];
|
||||
|
||||
for s_well in source_wells {
|
||||
let dest_wells = map(s_well);
|
||||
if let Some(dest_wells) = dest_wells {
|
||||
for d_well in dest_wells {
|
||||
records.push(TransferRecord {
|
||||
source_plate: src_barcode.to_string(),
|
||||
source_well: format!("{}{}", num_to_letters(s_well.0).unwrap(), s_well.1),
|
||||
destination_plate: dest_barcode.to_string(),
|
||||
destination_well: format!("{}{}", num_to_letters(d_well.0).unwrap(), d_well.1),
|
||||
volume: tr.volume,
|
||||
concentration: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
records
|
||||
}
|
||||
|
||||
pub fn records_to_csv(trs: Vec<TransferRecord>) -> Result<String, Box<dyn Error>> {
|
||||
let mut wtr = csv::WriterBuilder::new().from_writer(vec![]);
|
||||
for record in trs {
|
||||
wtr.serialize(record)?
|
||||
}
|
||||
let data = String::from_utf8(wtr.into_inner()?)?;
|
||||
Ok(data)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
pub mod csv;
|
||||
pub mod plate;
|
||||
pub mod plate_instances;
|
||||
pub mod transfer;
|
||||
pub mod transfer_region;
|
||||
pub mod util;
|
|
@ -0,0 +1,77 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Eq, Default, Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub struct Plate {
|
||||
pub plate_type: PlateType,
|
||||
pub plate_format: PlateFormat,
|
||||
}
|
||||
|
||||
impl Plate {
|
||||
pub fn new(plate_type: PlateType, plate_format: PlateFormat) -> Self {
|
||||
Plate {
|
||||
plate_type,
|
||||
plate_format,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> (u8, u8) {
|
||||
self.plate_format.size()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub enum PlateType {
|
||||
Source,
|
||||
Destination,
|
||||
}
|
||||
impl Default for PlateType {
|
||||
fn default() -> Self {
|
||||
Self::Source
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub enum PlateFormat {
|
||||
W6,
|
||||
W12,
|
||||
W24,
|
||||
W48,
|
||||
W96,
|
||||
W384,
|
||||
W1536,
|
||||
W3456,
|
||||
}
|
||||
impl Default for PlateFormat {
|
||||
fn default() -> Self {
|
||||
Self::W96
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for PlateFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PlateFormat::W6 => write!(f, "6"),
|
||||
PlateFormat::W12 => write!(f, "12"),
|
||||
PlateFormat::W24 => write!(f, "24"),
|
||||
PlateFormat::W48 => write!(f, "48"),
|
||||
PlateFormat::W96 => write!(f, "96"),
|
||||
PlateFormat::W384 => write!(f, "384"),
|
||||
PlateFormat::W1536 => write!(f, "1536"),
|
||||
PlateFormat::W3456 => write!(f, "3456"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlateFormat {
|
||||
pub fn size(&self) -> (u8, u8) {
|
||||
match self {
|
||||
PlateFormat::W6 => (2, 3),
|
||||
PlateFormat::W12 => (3, 4),
|
||||
PlateFormat::W24 => (4, 6),
|
||||
PlateFormat::W48 => (6, 8),
|
||||
PlateFormat::W96 => (8, 12),
|
||||
PlateFormat::W384 => (16, 24),
|
||||
PlateFormat::W1536 => (32, 48),
|
||||
PlateFormat::W3456 => (48, 72),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
use super::plate::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct PlateInstance {
|
||||
pub plate: Plate,
|
||||
#[serde(rename = "id_v7")]
|
||||
#[serde(default = "Uuid::now_v7")]
|
||||
id: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl PlateInstance {
|
||||
pub fn new(sort: PlateType, format: PlateFormat, name: String) -> Self {
|
||||
PlateInstance {
|
||||
plate: Plate {
|
||||
plate_type: sort,
|
||||
plate_format: format,
|
||||
},
|
||||
id: Uuid::now_v7(),
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_uuid(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn change_name(&mut self, new_name: String) {
|
||||
self.name = new_name;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Plate> for PlateInstance {
|
||||
fn from(value: Plate) -> Self {
|
||||
PlateInstance {
|
||||
plate: value,
|
||||
id: Uuid::now_v7(),
|
||||
name: "New Plate".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PlateInstance> for String {
|
||||
fn from(value: &PlateInstance) -> Self {
|
||||
// Could have other formatting here
|
||||
format!("{}, {}", value.name, value.plate.plate_format)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
use super::plate_instances::*;
|
||||
use super::transfer_region::*;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct Transfer {
|
||||
pub source_id: Uuid,
|
||||
pub dest_id: Uuid,
|
||||
pub name: String,
|
||||
#[serde(rename = "id_v7")]
|
||||
#[serde(default = "Uuid::now_v7")]
|
||||
pub id: Uuid,
|
||||
pub transfer_region: TransferRegion,
|
||||
#[serde(default = "default_volume")]
|
||||
pub volume: f32,
|
||||
}
|
||||
|
||||
impl Default for Transfer {
|
||||
fn default() -> Self {
|
||||
Transfer {
|
||||
source_id: Default::default(),
|
||||
dest_id: Default::default(),
|
||||
name: "New Transfer".to_string(),
|
||||
id: Default::default(),
|
||||
transfer_region: Default::default(),
|
||||
volume: 2.5f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_volume() -> f32 {
|
||||
2.5f32
|
||||
}
|
||||
|
||||
impl Transfer {
|
||||
pub fn new(
|
||||
source: PlateInstance,
|
||||
dest: PlateInstance,
|
||||
tr: TransferRegion,
|
||||
name: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
source_id: source.get_uuid(),
|
||||
dest_id: dest.get_uuid(),
|
||||
name,
|
||||
id: Uuid::now_v7(),
|
||||
transfer_region: tr,
|
||||
volume: 2.5,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_uuid(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
}
|
|
@ -0,0 +1,591 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::plate::Plate;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
pub struct CustomRegion {
|
||||
src: Vec<(u8, u8)>,
|
||||
dest: Vec<(u8, u8)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
pub enum Region {
|
||||
Rect((u8, u8), (u8, u8)),
|
||||
Point((u8, u8)),
|
||||
Custom(CustomRegion),
|
||||
}
|
||||
impl Default for Region {
|
||||
fn default() -> Self {
|
||||
Region::Point((1, 1))
|
||||
}
|
||||
}
|
||||
impl TryFrom<Region> for ((u8, u8), (u8, u8)) {
|
||||
type Error = &'static str;
|
||||
fn try_from(region: Region) -> Result<Self, Self::Error> {
|
||||
if let Region::Rect(c1, c2) = region {
|
||||
Ok((c1, c2))
|
||||
} else {
|
||||
// Should consider returning a degenerate rectangle here instead
|
||||
Err("Cannot convert this region to a rectangle, it was a point.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Corner = (u8, u8);
|
||||
type Rectangle = (Corner, Corner);
|
||||
impl Region {
|
||||
pub fn new_custom(transfers: &Vec<Rectangle>) -> Self {
|
||||
let mut src_pts: Vec<(u8, u8)> = Vec::with_capacity(transfers.len());
|
||||
let mut dest_pts: Vec<(u8, u8)> = Vec::with_capacity(transfers.len());
|
||||
|
||||
for transfer in transfers {
|
||||
src_pts.push(transfer.0);
|
||||
dest_pts.push(transfer.1);
|
||||
}
|
||||
|
||||
Region::Custom(CustomRegion {
|
||||
src: src_pts,
|
||||
dest: dest_pts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct TransferRegion {
|
||||
pub source_plate: Plate,
|
||||
pub source_region: Region, // Even if it is just a point, we don't want corners.
|
||||
pub dest_plate: Plate,
|
||||
pub dest_region: Region,
|
||||
pub interleave_source: (i8, i8),
|
||||
pub interleave_dest: (i8, i8),
|
||||
}
|
||||
|
||||
impl Default for TransferRegion {
|
||||
fn default() -> Self {
|
||||
TransferRegion {
|
||||
source_plate: Plate::default(),
|
||||
source_region: Region::default(),
|
||||
dest_plate: Plate::default(),
|
||||
dest_region: Region::default(),
|
||||
interleave_source: (1, 1),
|
||||
interleave_dest: (1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransferRegion {
|
||||
pub fn get_source_wells(&self) -> Vec<(u8, u8)> {
|
||||
match &self.source_region {
|
||||
Region::Rect(c1, c2) => {
|
||||
let mut wells = Vec::<(u8, u8)>::new();
|
||||
let (ul, br) = standardize_rectangle(c1, c2);
|
||||
let (interleave_i, interleave_j) = self.interleave_source;
|
||||
// NOTE: This will panic if either is 0!
|
||||
// We'll reassign these values (still not mutable) just in case.
|
||||
// This behaviour shouldn't be replicated for destination wells
|
||||
// because a zero step permits pooling.
|
||||
let (interleave_i, interleave_j) =
|
||||
(i8::max(interleave_i, 1), i8::max(interleave_j, 1));
|
||||
|
||||
for i in (ul.0..=br.0).step_by(i8::abs(interleave_i) as usize) {
|
||||
for j in (ul.1..=br.1).step_by(i8::abs(interleave_j) as usize) {
|
||||
// NOTE: It looks like we're ignoring negative interleaves,
|
||||
// because it wouldn't make a difference here---the same
|
||||
// wells will still be involved in the transfer.
|
||||
wells.push((i, j))
|
||||
}
|
||||
}
|
||||
wells
|
||||
}
|
||||
Region::Point(p) => vec![*p],
|
||||
Region::Custom(c) => c.src.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_destination_wells(&self) -> Vec<(u8, u8)> {
|
||||
let map = self.calculate_map();
|
||||
let source_wells = self.get_source_wells();
|
||||
|
||||
let mut wells = Vec::<(u8, u8)>::new();
|
||||
|
||||
// log::debug!("GDW:");
|
||||
for well in source_wells {
|
||||
if let Some(mut dest_wells) = map(well) {
|
||||
// log::debug!("Map {:?} to {:?}", well, dest_wells);
|
||||
wells.append(&mut dest_wells);
|
||||
}
|
||||
}
|
||||
// log::debug!("GDW END.");
|
||||
|
||||
wells
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)] // Resolving gives inherent associated type error
|
||||
pub fn calculate_map(&self) -> Box<dyn Fn((u8, u8)) -> Option<Vec<(u8, u8)>> + '_> {
|
||||
// By validating first, we have a stronger guarantee that
|
||||
// this function will not panic. :)
|
||||
// log::debug!("Validating: {:?}", self.validate());
|
||||
if let Err(msg) = self.validate() {
|
||||
eprintln!("{}", msg);
|
||||
eprintln!("This transfer will be empty.");
|
||||
return Box::new(|(_, _)| None);
|
||||
}
|
||||
|
||||
// log::debug!("What is ild? {:?}", self);
|
||||
let source_wells = self.get_source_wells();
|
||||
let il_dest = self.interleave_dest;
|
||||
let il_source = self.interleave_source;
|
||||
|
||||
let source_corners: ((u8, u8), (u8, u8)) = match self.source_region {
|
||||
Region::Point((x, y)) => ((x, y), (x, y)),
|
||||
Region::Rect(c1, c2) => (c1, c2),
|
||||
Region::Custom(_) => ((0, 0), (0, 0)),
|
||||
};
|
||||
let (source_ul, _) = standardize_rectangle(&source_corners.0, &source_corners.1);
|
||||
// This map is not necessarily injective or surjective,
|
||||
// but we will have these properties in certain cases.
|
||||
// If the transfer is not a pooling transfer (interleave == 0)
|
||||
// and simple then we *will* have injectivity.
|
||||
|
||||
// Non-replicate transfers:
|
||||
match &self.dest_region {
|
||||
Region::Point((x, y)) => {
|
||||
Box::new(move |(i, j)| {
|
||||
if source_wells.contains(&(i, j)) {
|
||||
// Validity here already checked by self.validate()
|
||||
Some(vec![(
|
||||
x + i
|
||||
.checked_sub(source_ul.0)
|
||||
.expect("Point cannot have been less than UL")
|
||||
.checked_div(il_source.0.unsigned_abs())
|
||||
.expect("Source interleave cannot be 0")
|
||||
.mul(il_dest.0.unsigned_abs()),
|
||||
y + j
|
||||
.checked_sub(source_ul.1)
|
||||
.expect("Point cannot have been less than UL")
|
||||
.checked_div(il_source.1.unsigned_abs())
|
||||
.expect("Source interleave cannot be 0")
|
||||
.mul(il_dest.1.unsigned_abs()),
|
||||
)])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
Region::Rect(c1, c2) => {
|
||||
Box::new(move |(i, j)| {
|
||||
if source_wells.contains(&(i, j)) {
|
||||
let possible_destination_wells = create_dense_rectangle(c1, c2);
|
||||
let (d_ul, d_br) = standardize_rectangle(c1, c2);
|
||||
let (s_ul, s_br) =
|
||||
standardize_rectangle(&source_corners.0, &source_corners.1);
|
||||
let s_dims = (
|
||||
s_br.0.checked_sub(s_ul.0).unwrap() + 1,
|
||||
s_br.1.checked_sub(s_ul.1).unwrap() + 1,
|
||||
);
|
||||
let d_dims = (
|
||||
d_br.0.checked_sub(d_ul.0).unwrap() + 1,
|
||||
d_br.1.checked_sub(d_ul.1).unwrap() + 1,
|
||||
);
|
||||
let number_used_src_wells = (
|
||||
// Number of used source wells
|
||||
(s_dims.0 + il_source.0.unsigned_abs() - 1)
|
||||
.div_euclid(il_source.0.unsigned_abs()),
|
||||
(s_dims.1 + il_source.1.unsigned_abs() - 1)
|
||||
.div_euclid(il_source.1.unsigned_abs()),
|
||||
);
|
||||
let count = (
|
||||
// How many times can we replicate?
|
||||
(1..)
|
||||
.position(|n| {
|
||||
n * number_used_src_wells.0 * il_dest.0.unsigned_abs() - il_dest.0.unsigned_abs()
|
||||
+ 1
|
||||
> d_dims.0
|
||||
})
|
||||
.unwrap() as u8,
|
||||
(1..)
|
||||
.position(|n| {
|
||||
n * number_used_src_wells.1 * il_dest.1.unsigned_abs() - il_dest.1.unsigned_abs()
|
||||
+ 1
|
||||
> d_dims.1
|
||||
})
|
||||
.unwrap() as u8,
|
||||
);
|
||||
let i = i
|
||||
.saturating_sub(s_ul.0)
|
||||
.saturating_div(il_source.0.unsigned_abs());
|
||||
let j = j
|
||||
.saturating_sub(s_ul.1)
|
||||
.saturating_div(il_source.1.unsigned_abs());
|
||||
|
||||
Some(
|
||||
possible_destination_wells
|
||||
.into_iter()
|
||||
.filter(|(x, _)| {
|
||||
x.checked_sub(d_ul.0).unwrap()
|
||||
% (number_used_src_wells.0 * il_dest.0.unsigned_abs()) // Counter along x
|
||||
== (il_dest.0.unsigned_abs() *i)
|
||||
% (number_used_src_wells.0 * il_dest.0.unsigned_abs())
|
||||
})
|
||||
.filter(|(_, y)| {
|
||||
y.checked_sub(d_ul.1).unwrap()
|
||||
% (number_used_src_wells.1 * il_dest.1.unsigned_abs()) // Counter along u
|
||||
== (il_dest.1.unsigned_abs() *j)
|
||||
% (number_used_src_wells.1 * il_dest.1.unsigned_abs())
|
||||
})
|
||||
.filter(|(x, y)| {
|
||||
// How many times have we replicated? < How many are we allowed
|
||||
// to replicate?
|
||||
x.checked_sub(d_ul.0)
|
||||
.unwrap()
|
||||
.div_euclid(number_used_src_wells.0 * il_dest.0.unsigned_abs())
|
||||
< count.0
|
||||
&& y.checked_sub(d_ul.1)
|
||||
.unwrap()
|
||||
.div_euclid(number_used_src_wells.1 * il_dest.1.unsigned_abs())
|
||||
< count.1
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
Region::Custom(c) => Box::new(move |(i, j)| {
|
||||
let src = c.src.clone();
|
||||
let dest = c.dest.clone();
|
||||
|
||||
let points: Vec<(u8, u8)> = src
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_index, (x, y))| *x == i && *y == j)
|
||||
.map(|(index, _)| dest[index])
|
||||
.collect();
|
||||
if points.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(points)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
// Checks if the region does anything suspect
|
||||
//
|
||||
// If validation fails, we pass a string to show to the user.
|
||||
//
|
||||
// We check:
|
||||
// - Are the wells in the source really there?
|
||||
// - In a replication region, do the source lengths divide the destination lengths?
|
||||
// - Are the interleaves valid?
|
||||
let il_source = self.interleave_source;
|
||||
let il_dest = self.interleave_dest;
|
||||
|
||||
match self.source_region {
|
||||
Region::Point(_) => return Ok(()), // Should make sure it's actually in the plate, leave for
|
||||
// later
|
||||
Region::Rect(s1, s2) => {
|
||||
// Check if all source wells exist:
|
||||
if s1.0 == 0 || s1.1 == 0 || s2.0 == 0 || s2.1 == 0 {
|
||||
return Err("Source region is out-of-bounds! (Too small)");
|
||||
}
|
||||
// Sufficient to check if the corners are in-bounds
|
||||
let source_max = self.source_plate.size();
|
||||
if s1.0 > source_max.0 || s2.0 > source_max.0 {
|
||||
return Err("Source region is out-of-bounds! (Too tall)");
|
||||
}
|
||||
if s1.1 > source_max.1 || s2.1 > source_max.1 {
|
||||
// log::debug!("s1.1: {}, max.1: {}", s1.1, source_max.1);
|
||||
return Err("Source region is out-of-bounds! (Too wide)");
|
||||
}
|
||||
}
|
||||
Region::Custom(_) => return Ok(()),
|
||||
}
|
||||
|
||||
if il_source.0 == 0 || il_dest.1 == 0 {
|
||||
return Err("Source interleave cannot be zero!");
|
||||
}
|
||||
|
||||
// Check if all destination wells exist:
|
||||
// NOT IMPLEMENTED
|
||||
// Should *not* happen in this function---otherwise
|
||||
// we'd get a nasty recursive loop.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_dense_rectangle(c1: &(u8, u8), c2: &(u8, u8)) -> Vec<(u8, u8)> {
|
||||
// Creates a vector of every point between two corners
|
||||
let (c1, c2) = standardize_rectangle(c1, c2);
|
||||
|
||||
let mut points = Vec::<(u8, u8)>::new();
|
||||
for i in c1.0..=c2.0 {
|
||||
for j in c1.1..=c2.1 {
|
||||
points.push((i, j));
|
||||
}
|
||||
}
|
||||
|
||||
points
|
||||
}
|
||||
|
||||
fn standardize_rectangle(c1: &(u8, u8), c2: &(u8, u8)) -> ((u8, u8), (u8, u8)) {
|
||||
let upper_left_i = u8::min(c1.0, c2.0);
|
||||
let upper_left_j = u8::min(c1.1, c2.1);
|
||||
let bottom_right_i = u8::max(c1.0, c2.0);
|
||||
let bottom_right_j = u8::max(c1.1, c2.1);
|
||||
(
|
||||
(upper_left_i, upper_left_j),
|
||||
(bottom_right_i, bottom_right_j),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use std::fmt;
|
||||
use std::ops::Mul;
|
||||
|
||||
#[cfg(debug_assertions)] // There should be no reason to print a transfer otherwise
|
||||
impl fmt::Display for TransferRegion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Source Plate:")?;
|
||||
let source_dims = self.source_plate.size();
|
||||
let source_wells = self.get_source_wells();
|
||||
let mut source_string = String::new();
|
||||
for i in 1..=source_dims.0 {
|
||||
for j in 1..=source_dims.1 {
|
||||
if source_wells.contains(&(i, j)) {
|
||||
source_string.push('x')
|
||||
} else {
|
||||
source_string.push('.')
|
||||
}
|
||||
}
|
||||
source_string.push('\n');
|
||||
}
|
||||
write!(f, "{}", source_string)?;
|
||||
|
||||
writeln!(f, "Dest Plate:")?;
|
||||
let dest_dims = self.dest_plate.size();
|
||||
let dest_wells = self.get_destination_wells();
|
||||
let mut dest_string = String::new();
|
||||
for i in 1..=dest_dims.0 {
|
||||
for j in 1..=dest_dims.1 {
|
||||
if dest_wells.contains(&(i, j)) {
|
||||
dest_string.push('x')
|
||||
} else {
|
||||
dest_string.push('.')
|
||||
}
|
||||
}
|
||||
dest_string.push('\n');
|
||||
}
|
||||
write!(f, "{}", dest_string)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
use crate::data::plate::*;
|
||||
use crate::data::transfer_region::*;
|
||||
|
||||
#[test]
|
||||
#[wasm_bindgen_test]
|
||||
fn test_simple_transfer() {
|
||||
let source = Plate::new(PlateType::Source, PlateFormat::W96);
|
||||
let destination = Plate::new(PlateType::Destination, PlateFormat::W384);
|
||||
|
||||
let transfer1 = TransferRegion {
|
||||
source_plate: source,
|
||||
source_region: Region::Rect((1, 1), (3, 3)),
|
||||
dest_plate: destination,
|
||||
dest_region: Region::Point((3, 3)),
|
||||
interleave_source: (1, 1),
|
||||
interleave_dest: (1, 1),
|
||||
};
|
||||
let transfer1_map = transfer1.calculate_map();
|
||||
assert_eq!(
|
||||
transfer1_map((1, 1)),
|
||||
Some(vec! {(3,3)}),
|
||||
"Failed basic shift transfer 1"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer1_map((1, 2)),
|
||||
Some(vec! {(3,4)}),
|
||||
"Failed basic shift transfer 2"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer1_map((2, 2)),
|
||||
Some(vec! {(4,4)}),
|
||||
"Failed basic shift transfer 3"
|
||||
);
|
||||
|
||||
let transfer2 = TransferRegion {
|
||||
source_plate: source,
|
||||
source_region: Region::Rect((1, 1), (3, 3)),
|
||||
dest_plate: destination,
|
||||
dest_region: Region::Point((3, 3)),
|
||||
interleave_source: (2, 2),
|
||||
interleave_dest: (1, 1),
|
||||
};
|
||||
let transfer2_map = transfer2.calculate_map();
|
||||
assert_eq!(
|
||||
transfer2_map((1, 1)),
|
||||
Some(vec! {(3,3)}),
|
||||
"Failed source interleave, type simple 1"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer2_map((1, 2)),
|
||||
None,
|
||||
"Failed source interleave, type simple 2"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer2_map((2, 2)),
|
||||
None,
|
||||
"Failed source interleave, type simple 3"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer2_map((3, 3)),
|
||||
Some(vec! {(4,4)}),
|
||||
"Failed source interleave, type simple 4"
|
||||
);
|
||||
|
||||
let transfer3 = TransferRegion {
|
||||
source_plate: source,
|
||||
source_region: Region::Rect((1, 1), (3, 3)),
|
||||
dest_plate: destination,
|
||||
dest_region: Region::Point((3, 3)),
|
||||
interleave_source: (1, 1),
|
||||
interleave_dest: (2, 3),
|
||||
};
|
||||
let transfer3_map = transfer3.calculate_map();
|
||||
assert_eq!(
|
||||
transfer3_map((1, 1)),
|
||||
Some(vec! {(3,3)}),
|
||||
"Failed destination interleave, type simple 1"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer3_map((2, 1)),
|
||||
Some(vec! {(5,3)}),
|
||||
"Failed destination interleave, type simple 2"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer3_map((1, 2)),
|
||||
Some(vec! {(3,6)}),
|
||||
"Failed destination interleave, type simple 3"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer3_map((2, 2)),
|
||||
Some(vec! {(5,6)}),
|
||||
"Failed destination interleave, type simple 4"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[wasm_bindgen_test]
|
||||
fn test_replicate_transfer() {
|
||||
let source = Plate::new(PlateType::Source, PlateFormat::W96);
|
||||
let destination = Plate::new(PlateType::Destination, PlateFormat::W384);
|
||||
|
||||
let transfer1 = TransferRegion {
|
||||
source_plate: source,
|
||||
source_region: Region::Rect((1, 1), (2, 2)),
|
||||
dest_plate: destination,
|
||||
dest_region: Region::Rect((2, 2), (11, 11)),
|
||||
interleave_source: (1, 1),
|
||||
interleave_dest: (3, 3),
|
||||
};
|
||||
let transfer1_map = transfer1.calculate_map();
|
||||
assert_eq!(
|
||||
transfer1_map((1, 1)),
|
||||
Some(vec! {(2, 2), (2, 8), (8, 2), (8, 8)}),
|
||||
"Failed type replicate 1"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer1_map((2, 1)),
|
||||
Some(vec! {(5, 2), (5, 8), (11, 2), (11, 8)}),
|
||||
"Failed type replicate 1"
|
||||
);
|
||||
|
||||
let transfer2 = TransferRegion {
|
||||
source_plate: Plate::new(PlateType::Source, PlateFormat::W384),
|
||||
dest_plate: Plate::new(PlateType::Destination, PlateFormat::W384),
|
||||
source_region: Region::Rect((1, 1), (2, 3)),
|
||||
dest_region: Region::Rect((2, 2), (11, 16)),
|
||||
interleave_source: (1, 1),
|
||||
interleave_dest: (2, 2),
|
||||
};
|
||||
let transfer2_source = transfer2.get_source_wells();
|
||||
let transfer2_dest = transfer2.get_destination_wells();
|
||||
assert_eq!(
|
||||
transfer2_source,
|
||||
vec![(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)],
|
||||
"Failed type replicate 2 source"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer2_dest,
|
||||
vec![
|
||||
(2, 2),
|
||||
(2, 8),
|
||||
(6, 2),
|
||||
(6, 8),
|
||||
(2, 4),
|
||||
(2, 10),
|
||||
(6, 4),
|
||||
(6, 10),
|
||||
(2, 6),
|
||||
(2, 12),
|
||||
(6, 6),
|
||||
(6, 12),
|
||||
(4, 2),
|
||||
(4, 8),
|
||||
(8, 2),
|
||||
(8, 8),
|
||||
(4, 4),
|
||||
(4, 10),
|
||||
(8, 4),
|
||||
(8, 10),
|
||||
(4, 6),
|
||||
(4, 12),
|
||||
(8, 6),
|
||||
(8, 12)
|
||||
],
|
||||
"Failed type replicate 2 destination"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[wasm_bindgen_test]
|
||||
fn test_pooling_transfer() {
|
||||
let transfer1 = TransferRegion {
|
||||
source_plate: Plate::new(PlateType::Source, PlateFormat::W384),
|
||||
dest_plate: Plate::new(PlateType::Destination, PlateFormat::W384),
|
||||
source_region: Region::Rect((1, 4), (3, 7)),
|
||||
dest_region: Region::Point((1, 9)),
|
||||
interleave_source: (1, 1),
|
||||
interleave_dest: (0, 2),
|
||||
};
|
||||
//let transfer1_source = transfer1.get_source_wells();
|
||||
let mut transfer1_dest = transfer1.get_destination_wells();
|
||||
transfer1_dest.sort();
|
||||
transfer1_dest.dedup(); // Makes our check easier, otherwise we have repeated wells
|
||||
let transfer1_map = transfer1.calculate_map();
|
||||
// Skipping source check---it's just 12 wells.
|
||||
assert_eq!(
|
||||
transfer1_dest,
|
||||
vec![(1, 9), (1, 11), (1, 13), (1, 15)],
|
||||
"Failed type pool 1 dest"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer1_map((2, 6)),
|
||||
Some(vec![(1, 13)]),
|
||||
"Failed type pool 1 map 1"
|
||||
);
|
||||
assert_eq!(
|
||||
transfer1_map((3, 7)),
|
||||
Some(vec![(1, 15)]),
|
||||
"Failed type pool 1 map 2"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
pub fn letters_to_num(letters: &str) -> Option<u8> {
|
||||
let mut num: u8 = 0;
|
||||
for (i, letter) in letters.to_ascii_uppercase().chars().rev().enumerate() {
|
||||
log::debug!("{}, {}", i, letter);
|
||||
let n = letter as u8;
|
||||
if !(65..=90).contains(&n) {
|
||||
return None;
|
||||
}
|
||||
num = num.checked_add((26_i32.pow(i as u32) * (n as i32 - 64)).try_into().ok()?)?;
|
||||
}
|
||||
Some(num)
|
||||
}
|
||||
pub fn num_to_letters(num: u8) -> Option<String> {
|
||||
if num == 0 {
|
||||
return None;
|
||||
} // Otherwise, we will not return none!
|
||||
// As another note, we can't represent higher than "IV" anyway;
|
||||
// thus there's no reason for a loop (26^n with n>1 will NOT occur).
|
||||
let mut text = "".to_string();
|
||||
let mut digit1 = num.div_euclid(26u8);
|
||||
let mut digit2 = num.rem_euclid(26u8);
|
||||
if digit1 > 0 && digit2 == 0u8 {
|
||||
digit1 -= 1;
|
||||
digit2 = 26;
|
||||
}
|
||||
if digit1 != 0 {
|
||||
text.push((64 + digit1) as char)
|
||||
}
|
||||
text.push((64 + digit2) as char);
|
||||
|
||||
Some(text.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{letters_to_num, num_to_letters, RegionDisplay};
|
||||
|
||||
#[test]
|
||||
fn test_letters_to_num() {
|
||||
assert_eq!(letters_to_num("D"), Some(4));
|
||||
assert_eq!(letters_to_num("d"), None);
|
||||
assert_eq!(letters_to_num("AD"), Some(26 + 4));
|
||||
assert_eq!(letters_to_num("CG"), Some(3 * 26 + 7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_to_letters() {
|
||||
println!("27 is {:?}", num_to_letters(27));
|
||||
assert_eq!(num_to_letters(1), Some("A".to_string()));
|
||||
assert_eq!(num_to_letters(26), Some("Z".to_string()));
|
||||
assert_eq!(num_to_letters(27), Some("AA".to_string()));
|
||||
assert_eq!(num_to_letters(111), Some("DG".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_l2n_and_n2l() {
|
||||
assert_eq!(
|
||||
num_to_letters(letters_to_num("A").unwrap()),
|
||||
Some("A".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
num_to_letters(letters_to_num("BJ").unwrap()),
|
||||
Some("BJ".to_string())
|
||||
);
|
||||
for i in 1..=255 {
|
||||
assert_eq!(letters_to_num(&num_to_letters(i).unwrap()), Some(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue