Skip to content

Commit

Permalink
Merge pull request #3 from Edinburgh-Genome-Foundry/dev
Browse files Browse the repository at this point in the history
v0.2.7  --  fix xls / xlsx compatibility
  • Loading branch information
veghp authored Jun 13, 2022
2 parents c9a6086 + 18f944b commit 928c714
Show file tree
Hide file tree
Showing 26 changed files with 1,559 additions and 178 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: build

on: [push]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: "3.9"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov coveralls
- name: Install
run: |
pip install -e .
- name: Test with pytest
run: |
python -m pytest --cov plateo --cov-report term-missing
- name: Coveralls
run: coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_SERVICE_NAME: github
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: python
python:
- "3.6"
- "3.9"
# command to install dependencies
before_install:
- pip install --upgrade pip
Expand Down
14 changes: 7 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<br /><br />
</p>

.. image:: https://travis-ci.org/Edinburgh-Genome-Foundry/Plateo.svg?branch=master
:target: https://travis-ci.org/Edinburgh-Genome-Foundry/Plateo
:alt: Travis CI build status
.. image:: https://github.com/Edinburgh-Genome-Foundry/Plateo/actions/workflows/build.yml/badge.svg
:target: https://github.com/Edinburgh-Genome-Foundry/Plateo/actions/workflows/build.yml
:alt: GitHub CI build status

.. image:: https://coveralls.io/repos/github/Edinburgh-Genome-Foundry/Plateo/badge.svg?branch=master
:target: https://coveralls.io/github/Edinburgh-Genome-Foundry/Plateo?branch=master
Expand Down Expand Up @@ -49,13 +49,13 @@ robot, we may be able to help too!
Installation
------------

Plateo can be installed by unzipping the source code in one directory and using this command: ::
Plateo can be installed from the Python Package Index with PIP: ::

(sudo) python setup.py install
pip install plateo

You can also install it directly from the Python Package Index with this command: ::
It can also be installed by unzipping the source code in one directory and using this command: ::

(sudo) pip install plateo
python setup.py install


Code organization
Expand Down
112 changes: 63 additions & 49 deletions plateo/AssemblyPlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@


class AssemblyPlan:

def __init__(self, assemblies, parts_data=None):
if isinstance(assemblies, (list, tuple)):
assemblies = OrderedDict(assemblies)
self.assemblies = assemblies
self.parts_data = parts_data

def all_parts_used(self):
return sorted(set([
part
for assembly in self.assemblies.values()
for part in assembly
]))
return sorted(
set([part for assembly in self.assemblies.values() for part in assembly])
)

def parts_without_data(self):
return {
Expand All @@ -30,14 +27,15 @@ def parts_without_data(self):
}

def assemblies_featuring(self, part):
return [name for (name, parts) in self.assemblies.items()
if part in parts]
return [name for (name, parts) in self.assemblies.items() if part in parts]

def assemblies_with_records(self):
return OrderedDict([
(name, [self.get_part_data(p)['record'] for p in parts])
for name, parts in self.assemblies.items()
])
return OrderedDict(
[
(name, [self.get_part_data(p)["record"] for p in parts])
for name, parts in self.assemblies.items()
]
)

def get_part_data(self, part_name):
if part_name not in self.parts_data:
Expand All @@ -60,35 +58,50 @@ def rename_part(self, part_name, new_name):
self.parts_data[new_name] = self.parts_data.pop(part_name)

@staticmethod
def from_spreadsheet(filepath=None, dataframe=None, sheet_name=0,
header=None):
def from_spreadsheet(filepath=None, dataframe=None, sheet_name=0, header=None):
if dataframe is None:
if filepath.lower().endswith('.csv'):
with open(filepath, 'r') as f:
dataframe = pandas.DataFrame([
line.split(',')
for line in f.read().split('\n')
])
else:
dataframe = pandas.read_excel(filepath, sheet_name=sheet_name,
header=header)
return AssemblyPlan(OrderedDict([
(row[0], [
str(e)
for e in row[1:]
if str(e) not in ['-', 'nan', 'None', '']
])
for i, row in dataframe.iterrows()
if str(row[0]).lower() not in ['nan', 'construct name',
'construct', 'none', '']
]))
if filepath.lower().endswith(".csv"):
with open(filepath, "r") as f:
dataframe = pandas.DataFrame(
[line.split(",") for line in f.read().split("\n")]
)
elif filepath.lower().endswith(".xls"):
dataframe = pandas.read_excel(
filepath, sheet_name=sheet_name, header=header, engine="xlrd"
)
else: # xlsx
dataframe = pandas.read_excel(
filepath, sheet_name=sheet_name, header=header, engine="openpyxl"
)
return AssemblyPlan(
OrderedDict(
[
(
row[0],
[
str(e)
for e in row[1:]
if str(e) not in ["-", "nan", "None", ""]
],
)
for i, row in dataframe.iterrows()
if str(row[0]).lower()
not in ["nan", "construct name", "construct", "none", ""]
]
)
)

def to_spreadsheet(self, path):
with open(path, "w") as f:
f.write("\n".join([("construct,parts")] + [
",".join([asm] + parts)
for asm, parts in self.assemblies.items()
]))
f.write(
"\n".join(
[("construct,parts")]
+ [
",".join([asm] + parts)
for asm, parts in self.assemblies.items()
]
)
)

def assemblies_per_part(self):
result = {}
Expand All @@ -107,14 +120,15 @@ def write_report(self, target):
sequenticon_dict = None
parts_length_dict = None
first_part_data = list(self.parts_data.values())[0]
if 'record' in first_part_data:
sequenticons = sequenticon_batch([
self.get_part_data(p)['record']
for p in all_parts_used
], output_format='html_image')
sequenticon_dict = OrderedDict([(name, icon)
for (name, icon) in sequenticons])
if (('record' in first_part_data) or ('size' in first_part_data)):
if "record" in first_part_data:
sequenticons = sequenticon_batch(
[self.get_part_data(p)["record"] for p in all_parts_used],
output_format="html_image",
)
sequenticon_dict = OrderedDict(
[(name, icon) for (name, icon) in sequenticons]
)
if ("record" in first_part_data) or ("size" in first_part_data):
parts_length_dict = {
part: human_seq_size(self.get_part_length(part))
for part in all_parts_used
Expand All @@ -125,13 +139,13 @@ def write_report(self, target):
sequenticon_dict=sequenticon_dict,
parts_length_dict=parts_length_dict,
all_parts_used=all_parts_used,
assemblies_per_part=assemblies_per_part
assemblies_per_part=assemblies_per_part,
)
report_writer.write_report(html, target)

def get_part_length(self, part_name):
data = self.get_part_data(part_name)
if 'size' in data:
return data['size']
if "size" in data:
return data["size"]
else:
return len(data['record'])
return len(data["record"])
35 changes: 12 additions & 23 deletions plateo/exporters/AssemblyPicklistGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def make_picklist(
):
source_wells = list(source_wells)
destination_wells = list(destination_wells)

# The second statement would quietly discard assemblies, so we check for length:
if len(assembly_plan.assemblies) > len(destination_wells):
raise ValueError("There are more assemblies than destination wells!")
destination_wells = destination_wells[: len(assembly_plan.assemblies)]

part_wells = {}
Expand All @@ -82,14 +86,10 @@ def make_picklist(
{
"missing_parts": {
part: {
"featured_in": assembly_plan.assemblies_featuring(
part
),
"featured_in": assembly_plan.assemblies_featuring(part),
"did_you_mean": [
(name, str(part_wells[name]))
for name in did_you_mean(
part, part_wells, min_score=50
)
for name in did_you_mean(part, part_wells, min_score=50)
],
}
for part in missing_parts
Expand All @@ -103,14 +103,10 @@ def make_picklist(
destination_well.data.construct = construct_name
for part in parts:
source_well = part_wells[part]
volume = self.volume_from_well(
source_well, assembly_plan.parts_data
)
volume = self.volume_from_well(source_well, assembly_plan.parts_data)
volume = round_at(volume, self.volume_rounding)
volume = max(volume, self.minimal_dispense_volume)
picklist.add_transfer(
source_well, destination_well, volume=volume
)
picklist.add_transfer(source_well, destination_well, volume=volume)

wells_over_desired_volume = []
if self.complement_to is not None:
Expand All @@ -125,16 +121,13 @@ def make_picklist(
)
if complement_well.is_empty:
raise TransferError(
"Well with COMPLEMENT/WATER is empty: %s."
% (complement_well)
"Well with COMPLEMENT/WATER is empty: %s." % (complement_well)
)
for well in destination_wells:
to_well = picklist.restricted_to(destination_well=well)
total_transfer_volume = to_well.total_transfered_volume()
complement_volume = (
self.complement_to
- total_transfer_volume
- self.buffer_volume
self.complement_to - total_transfer_volume - self.buffer_volume
)
if complement_volume < 0:
wells_over_desired_volume.append(well)
Expand All @@ -158,10 +151,7 @@ def make_picklist(
{
"wells_over_desired_volume": wells_over_desired_volume,
"duplicates": {
part_name: {
"wells": wells,
"selected": part_wells[part_name],
}
part_name: {"wells": wells, "selected": part_wells[part_name],}
for part_name, wells in duplicates.items()
},
},
Expand All @@ -187,8 +177,7 @@ def get_part_molar_weight(self, part_data):
def volume_from_well(self, well, parts_data):
if well.content.volume == 0:
raise ValueError(
("Cannot get anything from well %s " % well)
+ "which has a volume of 0"
("Cannot get anything from well %s " % well) + "which has a volume of 0"
)
part_mol = self.part_mol
part_g = self.part_g
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#sidebar: p Generated by Plateo version {{__version__}} on {{ pdf_tools.now() }}
#sidebar: p Generated by Plateo version {{ version }} on {{ pdf_tools.now() }}

.logos
img(style='width: 120px;' src="file:///{{ plateo_logo_url }}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#sidebar: p Generated by Plateo version {{__version__}} on {{ pdf_tools.now() }}
#sidebar: p Generated by Plateo version {{ version }} on {{ pdf_tools.now() }}

.logos
img(style='width: 120px;' src="file:///{{ plateo_logo_url }}")
Expand Down
30 changes: 19 additions & 11 deletions plateo/parsers/picklist_from_labcyte_echo_picklist_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from ..containers import get_plate_class
from ..PickList import PickList, Transfer

def picklist_from_labcyte_echo_picklist_file(filename=None, dataframe=None,
source_plate="auto",
dest_plate="auto"):

def picklist_from_labcyte_echo_picklist_file(
filename=None, dataframe=None, source_plate="auto", dest_plate="auto"
):
"""Return a Picklist from an ECHO picklist file.
Parameters
Expand All @@ -26,8 +27,11 @@ def picklist_from_labcyte_echo_picklist_file(filename=None, dataframe=None,
if dataframe is None:
if filename.lower().endswith(".csv"):
dataframe = pandas.read_csv(filename)
else:
dataframe = pandas.read_excel(filename)
elif filename.lower().endswith(".xls"):
dataframe = pandas.read_excel(filename, engine="xlrd")
else: # xlsx
dataframe = pandas.read_excel(filename, engine="openpyxl")

if source_plate == "auto":
nwells = infer_plate_size_from_wellnames(dataframe["Source Well"])
source_plate = get_plate_class(nwells)()
Expand All @@ -36,9 +40,13 @@ def picklist_from_labcyte_echo_picklist_file(filename=None, dataframe=None,
nwells = infer_plate_size_from_wellnames(dataframe["Destination Well"])
dest_plate = get_plate_class(nwells)()
dest_plate.name = "Destination"
return PickList([
Transfer(source_plate.wells[row["Source Well"]],
dest_plate.wells[row["Destination Well"]],
1e-9 * row["Volume"])
for i, row in dataframe.iterrows()
])
return PickList(
[
Transfer(
source_plate.wells[row["Source Well"]],
dest_plate.wells[row["Destination Well"]],
1e-9 * row["Volume"],
)
for i, row in dataframe.iterrows()
]
)
Loading

0 comments on commit 928c714

Please sign in to comment.