lib: auto creation from csv
This commit is contained in:
parent
d7c98d37a2
commit
6c8533f7a6
|
@ -637,6 +637,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"csv",
|
||||
"getrandom",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"rand",
|
||||
"regex",
|
||||
|
|
|
@ -14,3 +14,4 @@ serde_json = "1.0"
|
|||
csv = "1.2"
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
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::util::*;
|
||||
|
||||
use super::TransferRecord;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use super::TransferRecord;
|
||||
|
||||
|
||||
pub fn transfer_to_records(
|
||||
tr: &Transfer,
|
||||
|
@ -43,6 +44,21 @@ pub fn records_to_csv(trs: Vec<TransferRecord>) -> Result<String, Box<dyn Error>
|
|||
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> {
|
||||
let (header, data) = data.split_at(data.find('\n').unwrap());
|
||||
let modified: String = header.to_lowercase() + data;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
mod transfer_record;
|
||||
mod conversion;
|
||||
mod auto;
|
||||
|
||||
pub use transfer_record::volume_default;
|
||||
pub use transfer_record::TransferRecord;
|
||||
pub use conversion::*;
|
||||
|
||||
pub use auto::{auto, read_csv_auto};
|
||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::transfer::Transfer;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TransferRecord {
|
||||
#[serde(rename = "Source Plate", alias = "source plate", alias = "src plate")]
|
||||
pub source_plate: String,
|
||||
|
|
|
@ -8,6 +8,12 @@ pub struct CustomRegion {
|
|||
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)]
|
||||
pub enum Region {
|
||||
Rect((u8, u8), (u8, u8)),
|
||||
|
|
|
@ -33,7 +33,7 @@ pub fn num_to_letters(num: u8) -> Option<String> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{letters_to_num, num_to_letters, RegionDisplay};
|
||||
use super::{letters_to_num, num_to_letters};
|
||||
|
||||
#[test]
|
||||
fn test_letters_to_num() {
|
||||
|
|
Loading…
Reference in New Issue