Compare commits

..

1 Commits

Author SHA1 Message Date
Emilia Allison f50db96697
Precompute in calculate_map
Gitea Scan/plate-tool/pipeline/head This commit looks good Details
should help performance a wee bit
2024-11-16 22:19:03 -06:00
2 changed files with 86 additions and 80 deletions

View File

@ -51,7 +51,7 @@ However, it is much easier to click-and-drag the desired region.
If we click and hold on a well (see right pane), that specifies our start well. If we click and hold on a well (see right pane), that specifies our start well.
Then, we can drag and subsequently release on our desired end well. Then, we can drag and subsequently release on our desired end well.
Our selected wells will be bolded (thicker outline). Our selected wells will be highlighted in light blue for our source plate and light red for our destination plate.
You might also notice that some wells are hatched: You might also notice that some wells are hatched:
this indicates wells that will be used in the transfer. this indicates wells that will be used in the transfer.
Not all selected wells will necessarily be hatched, Not all selected wells will necessarily be hatched,
@ -68,29 +68,6 @@ When finished, click the "Save" button to commit these changes.
If you no longer need a transfer, select it as above and then click the "Delete" button. If you no longer need a transfer, select it as above and then click the "Delete" button.
#### Interleave
Interleave can be set for both source and destination plates.
Essentially, it can be read as "use every Nth well".
That is, a source row interleave of 2 would use every other row of the selected region.
It is permitted to have interleave in both dimensions on both plates in a single transfer.
#### Pooling
Consider a case where you want to condense an entire column in a source plate into just one row in the destination.
Instead of creating multiple transfers, it is sufficient to set the destination row interleave to 0.
An 0 interleave can be thought of as condensing that dimension to a point.
For this reason, 0 interleaves are not permitted on source plates (consider what this would represent).
Similarly, 0 interleaves are not permitted in replicate transfers (see next section).
#### Replicates
Where pooling permits a "many-to-one" transfer, replicates are "one-to-many".
This behavior is achieved by selecting a region in the destination plate instead of just a plate.
(If the corners of the destination region are not identical, it is a replicate.)
Plate-tool will attempt to fit as many copies into the selected destination region as space permits.
Partial copies will not be created.
That is, if a source region is 3 wells wide and a destination region is 7 wells wide,
2 copies will be made, and the 7th well in the destination will be left unused.
### Importing and Exporting ### Importing and Exporting
#### Export as CSV #### Export as CSV
@ -224,10 +201,3 @@ Here's how:
- You may need to check where `cargo` is installing binaries by default. For me, they're at `~/.cargo/bin`. - You may need to check where `cargo` is installing binaries by default. For me, they're at `~/.cargo/bin`.
If trunk is not automatically placed in your path, you would then run `/your/path/to/.cargo/bin/trunk serve`. If trunk is not automatically placed in your path, you would then run `/your/path/to/.cargo/bin/trunk serve`.
- You can instead run `trunk build --release` for a more performant binary. - You can instead run `trunk build --release` for a more performant binary.
## Bug Reports and Further Help
Any requests for help or assistance with bugs can be sent to platetool(at)ilia.moe
If reporting a bug, ideally include what it was you were trying to do and, if possible, a screenshot or any logs in
your browser's console.

View File

@ -182,61 +182,98 @@ impl TransferRegion {
let Well { row: i, col: j } = w; let Well { row: i, col: j } = w;
if source_wells.contains(&w) { if source_wells.contains(&w) {
let possible_destination_wells = create_dense_rectangle(c1, c2); let possible_destination_wells = create_dense_rectangle(c1, c2);
// Destination upper-left, bottom-right
let (d_ul, d_br) = standardize_rectangle(c1, c2); let (d_ul, d_br) = standardize_rectangle(c1, c2);
// Source upper-left, bottom-right
let (s_ul, s_br) = let (s_ul, s_br) =
standardize_rectangle(&source_corners.0, &source_corners.1); standardize_rectangle(&source_corners.0, &source_corners.1);
// Get base size of source region before interleave
let s_dims = ( let s_dims = (
s_br.row.checked_sub(s_ul.row).unwrap() + 1, s_br.row.checked_sub(s_ul.row).unwrap() + 1,
s_br.col.checked_sub(s_ul.col).unwrap() + 1, s_br.col.checked_sub(s_ul.col).unwrap() + 1,
); );
// Get base size of destination region before interleave
let d_dims = ( let d_dims = (
d_br.row.checked_sub(d_ul.row).unwrap() + 1, d_br.row.checked_sub(d_ul.row).unwrap() + 1,
d_br.col.checked_sub(d_ul.col).unwrap() + 1, d_br.col.checked_sub(d_ul.col).unwrap() + 1,
); );
// Precompute unsigned_abs: this should be cheap but
// this is called constantly in loops, plus prettier this way
let il_dest_uabs = (il_dest.0.unsigned_abs(), il_dest.1.unsigned_abs());
let il_source_uabs = (il_source.0.unsigned_abs(), il_source.1.unsigned_abs());
// Get the actual number of source wells in each dimension
// after considering interleave; essentially squish the air out of the
// region.
let number_used_src_wells = ( let number_used_src_wells = (
// Number of used source wells (s_dims.0 + il_source_uabs.0 - 1)
(s_dims.0 + il_source.0.unsigned_abs() - 1) .div_euclid(il_source_uabs.0),
.div_euclid(il_source.0.unsigned_abs()), (s_dims.1 + il_source_uabs.1 - 1)
(s_dims.1 + il_source.1.unsigned_abs() - 1) .div_euclid(il_source_uabs.1),
.div_euclid(il_source.1.unsigned_abs()),
); );
let count = (
// How many times can we replicate? // How many times can we replicate in each direction?
if il_dest.0.unsigned_abs() == 0 { // Lazy solution: just increment the number of replicates until it wouldn't
// fit anymore!
let count = {
let replicate_base_size = (
number_used_src_wells.0 * il_dest_uabs.0,
number_used_src_wells.1 * il_dest_uabs.1,
);
(
if il_dest_uabs.0 == 0 {
1 1
} else { } else {
(1..) (1..)
.position(|n| { .position(|n| {
(n * number_used_src_wells.0 * il_dest.0.unsigned_abs()) (n * replicate_base_size.0)
.saturating_sub(il_dest.0.unsigned_abs()) .saturating_sub(il_dest_uabs.0)
+ 1 + 1
> d_dims.0 > d_dims.0
}) })
.unwrap() as u8 .unwrap() as u8
}, },
if il_dest.1.unsigned_abs() == 0 { if il_dest_uabs.1 == 0 {
1 1
} else { } else {
(1..) (1..)
.position(|n| { .position(|n| {
(n * number_used_src_wells.1 * il_dest.1.unsigned_abs()) (n * replicate_base_size.1)
.saturating_sub(il_dest.1.unsigned_abs()) .saturating_sub(il_dest_uabs.1)
+ 1 + 1
> d_dims.1 > d_dims.1
}) })
.unwrap() as u8 .unwrap() as u8
}, },
); )
};
// Normalize our i,j (source coords) to a dense rectangle consisting only
// of points actually in the transfer.
let i = i let i = i
.saturating_sub(s_ul.row) .saturating_sub(s_ul.row)
.saturating_div(il_source.0.unsigned_abs()); .saturating_div(il_source_uabs.0);
let j = j let j = j
.saturating_sub(s_ul.col) .saturating_sub(s_ul.col)
.saturating_div(il_source.1.unsigned_abs()); .saturating_div(il_source_uabs.1);
let checked_il_dest = (u8::max(il_dest.0.unsigned_abs(), 1u8),
u8::max(il_dest.1.unsigned_abs(), 1u8)); // Should not matter because of validation,
// but a cheap way to prevent div by 0
let checked_il_dest = (
u8::max(il_dest_uabs.0, 1u8),
u8::max(il_dest_uabs.1, 1u8),
);
// Precompute multiplication
let row_modulus = number_used_src_wells.0 * checked_il_dest.0; let row_modulus = number_used_src_wells.0 * checked_il_dest.0;
let column_modulus = number_used_src_wells.1 * checked_il_dest.1; let column_modulus = number_used_src_wells.1 * checked_il_dest.1;
let residue_class = (
(il_dest_uabs.0 * i) % row_modulus,
(il_dest_uabs.1 * j) % column_modulus
);
Some( Some(
possible_destination_wells possible_destination_wells
@ -244,24 +281,22 @@ impl TransferRegion {
.filter(|Well { row: x, .. }| { .filter(|Well { row: x, .. }| {
x.checked_sub(d_ul.row).unwrap() x.checked_sub(d_ul.row).unwrap()
% row_modulus // Counter along x % row_modulus // Counter along x
== (il_dest.0.unsigned_abs() *i) == residue_class.0
% row_modulus
}) })
.filter(|Well { col: y, .. }| { .filter(|Well { col: y, .. }| {
y.checked_sub(d_ul.col).unwrap() y.checked_sub(d_ul.col).unwrap()
% column_modulus // Counter along u % column_modulus // Counter along u
== (il_dest.1.unsigned_abs() *j) == residue_class.1
% column_modulus
}) })
.filter(|Well { row: x, col: y }| { .filter(|Well { row: x, col: y }| {
// How many times have we replicated? < How many are we allowed // How many times have we replicated? < How many are we allowed
// to replicate? // to replicate?
x.checked_sub(d_ul.row).unwrap().div_euclid( x.checked_sub(d_ul.row).unwrap().div_euclid(row_modulus)
row_modulus < count.0
) < count.0 && y.checked_sub(d_ul.col)
&& y.checked_sub(d_ul.col).unwrap().div_euclid( .unwrap()
column_modulus .div_euclid(column_modulus)
) < count.1 < count.1
}) })
.collect(), .collect(),
) )
@ -319,8 +354,8 @@ impl TransferRegion {
return Err("Source region is out-of-bounds! (Too wide)"); return Err("Source region is out-of-bounds! (Too wide)");
} }
if il_dest == (0,0) { if il_dest == (0, 0) {
return Err("Refusing to pool both dimensions in a rectangular transfer!\nPlease select a point in the destination plate.") return Err("Refusing to pool both dimensions in a rectangular transfer!\nPlease select a point in the destination plate.");
} }
} }
Region::Custom(_) => return Ok(()), Region::Custom(_) => return Ok(()),
@ -343,7 +378,8 @@ fn create_dense_rectangle(c1: &Well, c2: &Well) -> Vec<Well> {
// Creates a vector of every point between two corners // Creates a vector of every point between two corners
let (c1, c2) = standardize_rectangle(c1, c2); let (c1, c2) = standardize_rectangle(c1, c2);
let mut points = Vec::<Well>::new(); let number_wells: usize = ((c2.row - c1.row + 1) as usize) * ((c2.col - c1.col + 1) as usize);
let mut points = Vec::<Well>::with_capacity(number_wells);
for i in c1.row..=c2.row { for i in c1.row..=c2.row {
for j in c1.col..=c2.col { for j in c1.col..=c2.col {
points.push(Well { row: i, col: j }); points.push(Well { row: i, col: j });