lib: auto creation from csv
This commit is contained in:
parent
d7c98d37a2
commit
6c8533f7a6
|
@ -637,6 +637,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
|
@ -14,3 +14,4 @@ serde_json = "1.0"
|
||||||
csv = "1.2"
|
csv = "1.2"
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
rand = { version = "0.8", features = ["small_rng"] }
|
rand = { version = "0.8", features = ["small_rng"] }
|
||||||
|
lazy_static = "1.4"
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
use super::{string_well_to_pt, TransferRecord, read_csv};
|
||||||
|
use crate::plate::{PlateFormat, PlateType};
|
||||||
|
use crate::plate_instances::PlateInstance;
|
||||||
|
use crate::transfer::Transfer;
|
||||||
|
use crate::transfer_region::{CustomRegion, Region, TransferRegion};
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub struct AutoOutput {
|
||||||
|
pub sources: Vec<PlateInstance>,
|
||||||
|
pub destinations: Vec<PlateInstance>,
|
||||||
|
pub transfers: Vec<Transfer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UniquePlates {
|
||||||
|
sources: Vec<PlateInstance>,
|
||||||
|
destinations: Vec<PlateInstance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const W96_COL_MAX: u8 = 12;
|
||||||
|
const W96_ROW_MAX: u8 = 8;
|
||||||
|
const W384_COL_MAX: u8 = 24;
|
||||||
|
const W384_ROW_MAX: u8 = 16;
|
||||||
|
const W1536_COL_MAX: u8 = 48;
|
||||||
|
const W1536_ROW_MAX: u8 = 32;
|
||||||
|
|
||||||
|
pub fn auto(records: &Vec<TransferRecord>) -> AutoOutput {
|
||||||
|
let unique_plates = find_unique_plates(records);
|
||||||
|
let transfers = get_transfer_for_all_pairs(records, &unique_plates);
|
||||||
|
|
||||||
|
AutoOutput { sources: unique_plates.sources, destinations: unique_plates.destinations, transfers }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_csv_auto(data: &str) -> AutoOutput {
|
||||||
|
let transfer_records = read_csv(data);
|
||||||
|
auto(&transfer_records)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates {
|
||||||
|
let mut source_names: HashSet<&str> = HashSet::new();
|
||||||
|
let mut destination_names: HashSet<&str> = HashSet::new();
|
||||||
|
|
||||||
|
for record in records {
|
||||||
|
source_names.insert(&record.source_well);
|
||||||
|
destination_names.insert(&record.destination_well);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sources: Vec<PlateInstance> = Vec::with_capacity(source_names.len());
|
||||||
|
for source_name in source_names {
|
||||||
|
let filtered_records: Vec<&TransferRecord> = records
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.source_plate == source_name)
|
||||||
|
.collect();
|
||||||
|
let format_guess = guess_plate_size(&filtered_records, PlateType::Source);
|
||||||
|
sources.push(PlateInstance::new(
|
||||||
|
PlateType::Source,
|
||||||
|
format_guess,
|
||||||
|
source_name.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut destinations: Vec<PlateInstance> = Vec::with_capacity(destination_names.len());
|
||||||
|
for destination_name in destination_names {
|
||||||
|
let filtered_records: Vec<&TransferRecord> = records
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.destination_plate == destination_name)
|
||||||
|
.collect();
|
||||||
|
let format_guess = guess_plate_size(&filtered_records, PlateType::Destination);
|
||||||
|
destinations.push(PlateInstance::new(
|
||||||
|
PlateType::Destination,
|
||||||
|
format_guess,
|
||||||
|
destination_name.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePlates {
|
||||||
|
sources,
|
||||||
|
destinations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guess_plate_size(plate_filtered_records: &[&TransferRecord], which: PlateType) -> PlateFormat {
|
||||||
|
let mut guess = PlateFormat::W96; // Never guess smaller than 96
|
||||||
|
for record in plate_filtered_records {
|
||||||
|
if let Some((row, col)) = string_well_to_pt(match which {
|
||||||
|
PlateType::Source => &record.source_well,
|
||||||
|
PlateType::Destination => &record.destination_well,
|
||||||
|
}) {
|
||||||
|
if row > W1536_ROW_MAX || col > W1536_COL_MAX {
|
||||||
|
return PlateFormat::W3456;
|
||||||
|
} else if row > W384_ROW_MAX || col > W384_COL_MAX {
|
||||||
|
guess = PlateFormat::W1536;
|
||||||
|
} else if row > W96_ROW_MAX || col > W96_COL_MAX {
|
||||||
|
guess = PlateFormat::W384;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guess
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transfer_for_all_pairs(
|
||||||
|
records: &[TransferRecord],
|
||||||
|
unique_plates: &UniquePlates,
|
||||||
|
) -> Vec<Transfer> {
|
||||||
|
let mut transfers: Vec<Transfer> = Vec::new();
|
||||||
|
for source_instance in &unique_plates.sources {
|
||||||
|
for destination_instance in &unique_plates.destinations {
|
||||||
|
if let Some(transfer) =
|
||||||
|
get_transfer_for_pair(records, source_instance, destination_instance)
|
||||||
|
{
|
||||||
|
transfers.push(transfer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transfers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transfer_for_pair(
|
||||||
|
records: &[TransferRecord],
|
||||||
|
source: &PlateInstance,
|
||||||
|
destination: &PlateInstance,
|
||||||
|
) -> Option<Transfer> {
|
||||||
|
let source_name: &str = &source.name;
|
||||||
|
let destination_name: &str = &destination.name;
|
||||||
|
|
||||||
|
let mut filtered_records = records
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.source_plate == source_name && x.destination_plate == destination_name)
|
||||||
|
.peekable();
|
||||||
|
|
||||||
|
if filtered_records.peek().is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut source_wells: HashSet<(u8, u8)> = HashSet::new();
|
||||||
|
let mut destination_wells: HashSet<(u8, u8)> = HashSet::new();
|
||||||
|
for record in filtered_records {
|
||||||
|
let source_point_opt = string_well_to_pt(&record.source_well);
|
||||||
|
let destination_point_opt = string_well_to_pt(&record.destination_well);
|
||||||
|
|
||||||
|
if source_point_opt.and(destination_point_opt).is_some() {
|
||||||
|
let source_point = source_point_opt.unwrap();
|
||||||
|
let destination_point = destination_point_opt.unwrap();
|
||||||
|
|
||||||
|
source_wells.insert(source_point);
|
||||||
|
destination_wells.insert(destination_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let source_wells_vec: Vec<(u8, u8)> = source_wells.into_iter().collect();
|
||||||
|
let destination_wells_vec: Vec<(u8, u8)> = destination_wells.into_iter().collect();
|
||||||
|
|
||||||
|
let custom_region: Region =
|
||||||
|
Region::Custom(CustomRegion::new(source_wells_vec, destination_wells_vec));
|
||||||
|
|
||||||
|
let transfer_region = TransferRegion {
|
||||||
|
source_plate: source.plate,
|
||||||
|
dest_plate: destination.plate,
|
||||||
|
interleave_source: (1, 1),
|
||||||
|
interleave_dest: (1, 1),
|
||||||
|
source_region: custom_region.clone(),
|
||||||
|
dest_region: custom_region,
|
||||||
|
};
|
||||||
|
|
||||||
|
let transfer_name = format!("{} to {}", source.name, destination.name);
|
||||||
|
|
||||||
|
Some(Transfer::new(
|
||||||
|
source.clone(),
|
||||||
|
destination.clone(),
|
||||||
|
transfer_region,
|
||||||
|
transfer_name,
|
||||||
|
))
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::transfer::Transfer;
|
use crate::transfer::Transfer;
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
|
|
||||||
|
use super::TransferRecord;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use super::TransferRecord;
|
|
||||||
|
|
||||||
|
|
||||||
pub fn transfer_to_records(
|
pub fn transfer_to_records(
|
||||||
tr: &Transfer,
|
tr: &Transfer,
|
||||||
|
@ -43,6 +44,21 @@ pub fn records_to_csv(trs: Vec<TransferRecord>) -> Result<String, Box<dyn Error>
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_well_to_pt(input: &str) -> Option<(u8, u8)> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap();
|
||||||
|
}
|
||||||
|
if let Some(c1) = REGEX.captures(input) {
|
||||||
|
if let (Some(row), Some(col)) = (letters_to_num(&c1[1]), c1[2].parse::<u8>().ok()) {
|
||||||
|
Some((row, col))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_csv(data: &str) -> Vec<TransferRecord> {
|
pub fn read_csv(data: &str) -> Vec<TransferRecord> {
|
||||||
let (header, data) = data.split_at(data.find('\n').unwrap());
|
let (header, data) = data.split_at(data.find('\n').unwrap());
|
||||||
let modified: String = header.to_lowercase() + data;
|
let modified: String = header.to_lowercase() + data;
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
mod transfer_record;
|
mod transfer_record;
|
||||||
mod conversion;
|
mod conversion;
|
||||||
|
mod auto;
|
||||||
|
|
||||||
pub use transfer_record::volume_default;
|
pub use transfer_record::volume_default;
|
||||||
pub use transfer_record::TransferRecord;
|
pub use transfer_record::TransferRecord;
|
||||||
pub use conversion::*;
|
pub use conversion::*;
|
||||||
|
|
||||||
|
pub use auto::{auto, read_csv_auto};
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::transfer::Transfer;
|
use crate::transfer::Transfer;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct TransferRecord {
|
pub struct TransferRecord {
|
||||||
#[serde(rename = "Source Plate", alias = "source plate", alias = "src plate")]
|
#[serde(rename = "Source Plate", alias = "source plate", alias = "src plate")]
|
||||||
pub source_plate: String,
|
pub source_plate: String,
|
||||||
|
|
|
@ -8,6 +8,12 @@ pub struct CustomRegion {
|
||||||
dest: Vec<(u8, u8)>,
|
dest: Vec<(u8, u8)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CustomRegion {
|
||||||
|
pub fn new(src: Vec<(u8, u8)>, dest: Vec<(u8, u8)>) -> Self {
|
||||||
|
CustomRegion { src, dest }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||||
pub enum Region {
|
pub enum Region {
|
||||||
Rect((u8, u8), (u8, u8)),
|
Rect((u8, u8), (u8, u8)),
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn num_to_letters(num: u8) -> Option<String> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{letters_to_num, num_to_letters, RegionDisplay};
|
use super::{letters_to_num, num_to_letters};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_letters_to_num() {
|
fn test_letters_to_num() {
|
||||||
|
|
Loading…
Reference in New Issue