Add some doc comments to lib
Gitea Scan/plate-tool/pipeline/head This commit looks good Details

This commit is contained in:
Emilia Allison 2024-08-04 14:48:45 -04:00
parent 94386a0485
commit ce6e88a347
Signed by: emilia
GPG Key ID: 7A3F8997BFE894E0
5 changed files with 64 additions and 3 deletions

View File

@ -2,9 +2,10 @@ use serde::Serialize;
use super::TransferRecord; use super::TransferRecord;
// Format preferred by the Echo Client when using the Pick-List function. /// Format preferred by the Echo Client when using the Pick-List function.
// Note that this does not export plate info! ///
// Exports of this type should combine all transfers between the two actively selected plates. /// Note that this does not export plate info!
/// Exports of this type should combine all transfers between the two actively selected plates.
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct EchoClientTransferRecord { pub struct EchoClientTransferRecord {
#[serde(rename = "SrcWell")] #[serde(rename = "SrcWell")]

View File

@ -1,3 +1,8 @@
//! Auto module for importing picklists without creating plates first
//!
//! Before the auto module, it was necessary for a user to create the plates
//! used in an imported transfer before import.
use super::{string_well_to_pt, TransferRecord, read_csv}; use super::{string_well_to_pt, TransferRecord, read_csv};
use crate::plate::{PlateFormat, PlateType}; use crate::plate::{PlateFormat, PlateType};
use crate::plate_instances::PlateInstance; use crate::plate_instances::PlateInstance;
@ -12,6 +17,7 @@ pub struct AutoOutput {
pub transfers: Vec<Transfer>, pub transfers: Vec<Transfer>,
} }
/// Two lists of plates that are guaranteed to be unique (no duplicate plates)
struct UniquePlates { struct UniquePlates {
sources: Vec<PlateInstance>, sources: Vec<PlateInstance>,
destinations: Vec<PlateInstance>, destinations: Vec<PlateInstance>,
@ -24,6 +30,12 @@ const W384_ROW_MAX: u8 = 16;
const W1536_COL_MAX: u8 = 48; const W1536_COL_MAX: u8 = 48;
const W1536_ROW_MAX: u8 = 32; const W1536_ROW_MAX: u8 = 32;
/// Main function for the auto module
/// Takes a list of pre-deserialized transfer records and generates output
/// This output is 3 lists of:
/// 1. The source plates used in the picklist
/// 2. The destination plates used in the picklist
/// 3. All of the transfers between those plates
pub fn auto(records: &[TransferRecord]) -> AutoOutput { pub fn auto(records: &[TransferRecord]) -> AutoOutput {
let unique_plates = find_unique_plates(records); let unique_plates = find_unique_plates(records);
let transfers = get_transfer_for_all_pairs(records, &unique_plates); let transfers = get_transfer_for_all_pairs(records, &unique_plates);
@ -31,11 +43,18 @@ pub fn auto(records: &[TransferRecord]) -> AutoOutput {
AutoOutput { sources: unique_plates.sources, destinations: unique_plates.destinations, transfers } AutoOutput { sources: unique_plates.sources, destinations: unique_plates.destinations, transfers }
} }
/// Helper function that reads a CSV and immediately dumps the resulting transfer records
/// into the auto function defined in this module.
pub fn read_csv_auto(data: &str) -> AutoOutput { pub fn read_csv_auto(data: &str) -> AutoOutput {
let transfer_records = read_csv(data); let transfer_records = read_csv(data);
auto(&transfer_records) auto(&transfer_records)
} }
/// Looks through a list of transfer records and generates two lists
/// of all of the unique plates found in those transfers.
/// Worth noting that the Transfer struct requires two associated PlateInstances;
/// these PlateInstance structs are generated here---they are not present in a TransferRecord.
/// See notes on the UniquePlates struct
fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates { fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates {
let mut source_names: HashSet<&str> = HashSet::new(); let mut source_names: HashSet<&str> = HashSet::new();
let mut destination_names: HashSet<&str> = HashSet::new(); let mut destination_names: HashSet<&str> = HashSet::new();
@ -79,6 +98,12 @@ fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates {
} }
} }
/// Tries to guess the format of a plate given the southeastern-est well used.
/// A picklist does not necessarily contain plate format info; this method guesses
/// based on the well furthest from A1 that exists in a transfer.
/// It's possible for a 1536 well plate to be seen as a 384 well if all of the wells
/// used could have fit in the top-left quadrant of a 384 well plate
/// (i.e. all lower than P24 in this case)
fn guess_plate_size(plate_filtered_records: &[&TransferRecord], which: PlateType) -> PlateFormat { fn guess_plate_size(plate_filtered_records: &[&TransferRecord], which: PlateType) -> PlateFormat {
let mut guess = PlateFormat::W96; // Never guess smaller than 96 let mut guess = PlateFormat::W96; // Never guess smaller than 96
for record in plate_filtered_records { for record in plate_filtered_records {
@ -116,6 +141,17 @@ fn get_transfer_for_all_pairs(
transfers transfers
} }
/// Given a source and destination plate, get all of the wells transferred
/// from source to destination and turn this into a Transfer.
///
/// Note that even if you have what looks like two separate transfers,
/// this will join all transfers for a given source-dest pair.
/// For example:
/// Consider expanding all of src:A1 to the column dst:A
/// and src:B1 -> row dst:1
/// If you were making this transfer in plate tool, it would be easiest
/// to do this as two separate transfers.
/// On import, this would be seen as one transfer.
fn get_transfer_for_pair( fn get_transfer_for_pair(
records: &[TransferRecord], records: &[TransferRecord],
source: &PlateInstance, source: &PlateInstance,

View File

@ -52,9 +52,12 @@ pub fn records_to_echo_client_csv(trs: Vec<TransferRecord>) -> Result<String, Bo
Ok(data) Ok(data)
} }
/// Converts "spreadsheet format" well identification to coordinates
pub fn string_well_to_pt(input: &str) -> Option<(u8, u8)> { pub fn string_well_to_pt(input: &str) -> Option<(u8, u8)> {
lazy_static! { lazy_static! {
static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap(); static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap();
// Can this be removed?
static ref REGEX_ALT: Regex = Regex::new(r"(\d+)").unwrap(); static ref REGEX_ALT: Regex = Regex::new(r"(\d+)").unwrap();
} }
if let Some(c1) = REGEX.captures(input) { if let Some(c1) = REGEX.captures(input) {

View File

@ -1,3 +1,12 @@
//! This contains the `mangle_headers` function and its helpers
//!
//! Mangling headers is necessary to normalize CSV column names before
//! deserialization can be done by Serde.
//! Field detection occurs in `detect_field`.
//!
//! As of lib 0.4.0, unrecognized headers are ignored and left untouched by mangling.
//! However, they are not passed through to any subsequent exports from plate-tool.
pub fn mangle_headers(data: &str) -> String { pub fn mangle_headers(data: &str) -> String {
let (header, rows) = data.split_at(data.find('\n').unwrap()); let (header, rows) = data.split_at(data.find('\n').unwrap());
let fields = header.trim().split(","); let fields = header.trim().split(",");

View File

@ -2,6 +2,10 @@ use serde::{Deserialize, Serialize};
use crate::{plate::PlateFormat, transfer::Transfer, util::num_to_letters}; use crate::{plate::PlateFormat, transfer::Transfer, util::num_to_letters};
/// Represents a single line of a CSV picklist.
/// In practice, this is generated from the deserialize intermediate.
/// A list of `TransferRecord`s can be used to create a `transfer::Transfer` struct;
/// see the `auto` module for this.
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
pub struct TransferRecord { pub struct TransferRecord {
#[serde(rename = "Source Barcode")] #[serde(rename = "Source Barcode")]
@ -18,6 +22,12 @@ pub struct TransferRecord {
pub concentration: Option<f32>, pub concentration: Option<f32>,
} }
/// The deserialization intermediate is generated from a mangled CSV s.t.
/// the column headers are standardized to those given to the derive macro.
///
/// `TransferRecord` can *always* be generated from the intermediate.
/// Currently there's no reason why the inverse couldn't be true,
/// but there's no reason to convert back into the deserialize-only intermediate.
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct TransferRecordDeserializeIntermediate { pub struct TransferRecordDeserializeIntermediate {
#[serde(rename = "sourceplate")] #[serde(rename = "sourceplate")]
@ -78,6 +88,7 @@ impl From<TransferRecordDeserializeIntermediate> for TransferRecord {
} }
impl TransferRecordDeserializeIntermediate { impl TransferRecordDeserializeIntermediate {
/// Used to cull malformed picklist entries that lack required information
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.source_plate.is_empty() self.source_plate.is_empty()
|| self.destination_plate.is_empty() || self.destination_plate.is_empty()
@ -95,6 +106,7 @@ fn numeric_well_to_alphanumeric(input: u16, pformat: PlateFormat) -> Option<Stri
Some(format!("{}{}", row_str, column)) Some(format!("{}{}", row_str, column))
} }
// Why is this here?
pub fn volume_default() -> f32 { pub fn volume_default() -> f32 {
Transfer::default().volume Transfer::default().volume
} }