Compare commits
1 Commits
beta-relea
...
dev
Author | SHA1 | Date |
---|---|---|
Emilia Allison | f50db96697 |
32
README.md
32
README.md
|
@ -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.
|
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
Loading…
Reference in New Issue