def test_protocol_equality(self): # Set up a protocol. p1 = Protocol() p1.add_instrument('A', 'p200') p1.add_container('C1', 'tiprack.p200') p1.add_container('A1', 'microplate.96') p1.calibrate('A1', x=1, y=2, z=3) p1.calibrate_instrument('A', top=0, blowout=10) p1.transfer('A1:A1', 'A1:A2', ul=100) p1.transfer('A1:A2', 'A1:A3', ul=80) # And a copy. p2 = Protocol() p2.add_container('A1', 'microplate.96') p2.add_container('C1', 'tiprack.p200') p2.add_instrument('A', 'p200') p2.calibrate('A1', x=1, y=2, z=3) p2.calibrate_instrument('A', top=0, blowout=10) p2.transfer('A1:A1', 'A1:A2', ul=100) p2.transfer('A1:A2', 'A1:A3', ul=80) # They're identical. self.assertEqual(p1, p2) # Make a change. p2.add_instrument('B', 'p10') # No longer identical. self.assertNotEqual(p1, p2)
class ProtocolTest(unittest.TestCase): def setUp(self): self.protocol = Protocol() self.maxDiff = None @property def instructions(self): return self.protocol._commands def test_normalize_address(self): self.protocol.add_container('A1', 'microplate.96', label="Output") label = self.protocol._normalize_address('Output:A1') self.assertEqual(label, ((0, 0), (0, 0))) slot = self.protocol._normalize_address('A1:A1') self.assertEqual(slot, ((0, 0), (0, 0))) def test_info(self): name = "Foo Bar" desc = "Lorem ipsum dolor set amet." auth = "Jane Doe" self.protocol.set_info(name=name, description=desc, author=auth) i = self.protocol.info self.assertEqual(i['name'], name) self.assertEqual(i['description'], desc) self.assertEqual(i['author'], auth) self.assertTrue('created' in i) self.assertTrue('updated' in i) def test_humanize_address(self): self.protocol.add_container("A1", 'microplate.96', label="LaBeL") with self.assertRaises(x.ContainerConflict): self.protocol.add_container("A2", 'microplate.96', label="label") self.protocol.add_container("A2", 'microplate.96', label="stuff") lA1 = self.protocol.humanize_address(('label', 'A1')) sA1 = self.protocol.humanize_address(('STUFF', 'A1')) self.assertEqual(lA1, 'LaBeL:A1') self.assertEqual(sA1, 'stuff:A1') def test_transfer(self): """ Basic transfer. """ self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('B1', 'microplate.96') self.protocol.add_instrument('A', 'p200') self.protocol.add_instrument('B', 'p20') self.protocol.transfer('A1:A1', 'B1:B1', ul=100, tool='p200') expected = [{ 'command': 'transfer', 'tool': 'p200', 'volume': 100, 'start': ((0, 0), (0, 0)), 'end': ((1, 0), (1, 0)), 'blowout': True, 'touchtip': True }] self.assertEqual(self.instructions, expected) def test_transfer_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.transfer('A1:A1', 'A1:A2', ul=10) def test_transfer_without_volume(self): self.protocol.add_instrument('A', 'p200') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1") def test_transfer_zero_volume(self): with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1", ul=0) with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1", ml=0) def test_transfer_conflicting_volume(self): with self.assertRaises(ValueError): self.protocol.transfer("A1:A1", "A1:A1", ul=1, ml=1) def test_transfer_group(self): """ Transfer group. """ expected = [{ 'command': 'transfer_group', 'tool': 'p20', 'transfers': [ { 'volume': 15, 'start': ((0, 0), (0, 0)), # A1:A1 'end': ((0, 0), (1, 0)), # A1:B1 'blowout': True, 'touchtip': True }, { 'volume': 10, 'start': ((0, 0), (0, 1)), # A1:A2 'end': ((0, 0), (1, 1)), # A1:B2 'blowout': True, 'touchtip': True }, { 'volume': 12, 'start': ((0, 0), (0, 2)), # A1:A3 'end': ((0, 0), (1, 2)), # A1:B3 'blowout': False, 'touchtip': True }, { 'volume': 12, 'start': ((0, 0), (0, 3)), # A1:A4 'end': ((0, 0), (1, 3)), # A1:B4 'blowout': True, 'touchtip': True }, { 'volume': 12, 'start': ((0, 0), (0, 4)), # A1:A5 'end': ((0, 0), (1, 4)), # A1:B5 'blowout': True, 'touchtip': True } ] }] self.protocol.add_container('A1', 'microplate.96', label="Label") self.protocol.add_instrument('A', 'p20') self.protocol.transfer_group( ('A1:A1', 'A1:B1', {'ul': 15}), ('A1:A2', 'A1:B2', {'ul': 10}), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=12, tool='p20' ) self.assertEqual(self.instructions, expected) def test_transfer_group_without_pipette(self): self.protocol.add_instrument('A', 'p200') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.transfer_group( ('A1:A1', 'A1:B1', {'ul': 15}), ('A1:A2', 'A1:B2', {'ml': 1}), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=12, tool='p10' ) def test_transfer_group_without_volume(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.add_instrument('A', 'p10') self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), tool='p10' ) def test_transfer_group_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=0, tool='p10' ) with self.assertRaises(ValueError): self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ml=0, tool='p10' ) def test_transfer_group_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.transfer_group( ('A1:A1', 'A1:B1'), ('A1:A2', 'A1:B2'), ('A1:A3', 'A1:B3', {'blowout': False}), ('A1:A4', 'A1:B4'), ('A1:A5', 'A1:B5'), ul=5, ml=4, tool='p10' ) def test_distribute(self): self.protocol.add_instrument('A', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.distribute( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1'), ('A1:D1', {'ul': 30}), ul=20 ) expected = [{ 'command': 'distribute', 'tool': 'p200', 'start': ((0, 0), (0, 0)), 'transfers': [ { 'volume': 50, 'end': ((0, 0), (1, 0)), # A1:B1 'blowout': True, 'touchtip': True }, { 'volume': 20, # Default 'end': ((0, 0), (2, 0)), # A1:C1 'blowout': True, 'touchtip': True }, { 'volume': 30, 'end': ((0, 0), (3, 0)), # A1:D1 'blowout': True, 'touchtip': True } ] }] self.assertEqual(self.instructions, expected) def test_distribute_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.distribute( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 10}) ) def test_distribute_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.distribute( 'A1:A1', ('A1:B1', {'ul': 4}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 0}) ) def test_distribute_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.distribute( 'A1:A1', ('A1:B1'), ('A1:C1'), ('A1:D1', {'ul': 10, 'ml': 5}), ul=5 ) def test_consolidate(self): """ Consolidate. """ self.protocol.add_container('A1', 'microplate.96') self.protocol.add_instrument('A', 'p200') self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1', {'ul': 25}), ('A1:D1'), ul=30 ) expected = [{ 'command': 'consolidate', 'tool': 'p200', 'end': ((0, 0), (0, 0)), 'transfers': [ { 'volume': 50, 'start': ((0, 0), (1, 0)), # A1:B1 'blowout': True, 'touchtip': True }, { 'volume': 25, 'start': ((0, 0), (2, 0)), # A1:C1 'blowout': True, 'touchtip': True }, { 'volume': 30, 'start': ((0, 0), (3, 0)), # A1:D1 'blowout': True, 'touchtip': True } ] }] self.assertEqual(self.instructions, expected) def test_consolidate_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 50}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 10}) ) def test_consolidate_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 4}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 0}) ) def test_consolidate_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.consolidate( 'A1:A1', ('A1:B1', {'ul': 5}), ('A1:C1', {'ul': 5}), ('A1:D1', {'ul': 10, 'ml': 5}) ) def test_mix(self): """ Mix. """ self.protocol.add_container('A1', 'microplate.96') self.protocol.add_instrument('A', 'p200') self.protocol.mix('A1:A1', ul=50, repetitions=10) expected = [{ 'command': 'mix', 'tool': 'p200', 'start': ((0, 0), (0, 0)), # A1:A1 'blowout': True, 'touchtip': True, 'volume': 50, 'reps': 10 }] self.assertEqual(self.instructions, expected) def test_mix_without_pipette(self): self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(x.InstrumentMissing): self.protocol.mix('A1:A1', ul=50, repetitions=10, tool='p200') def test_mix_without_volume(self): self.protocol.add_container('A1', 'microplate.96') self.protocol.add_instrument('A', 'p10') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', repetitions=10, tool='p10') def test_mix_zero_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', ul=0, repetitions=10, tool='p10') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', ml=0, repetitions=10, tool='p10') def test_mix_conflicting_volume(self): self.protocol.add_instrument('A', 'p10') self.protocol.add_container('A1', 'microplate.96') with self.assertRaises(ValueError): self.protocol.mix('A1:A1', ul=1, ml=1, repetitions=10, tool='p10') def test_protocol_run_twice(self): """ Run a protocol twice without error. """ self.protocol.add_instrument('A', 'p200') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.calibrate('A1', x=1, y=2, z=3) self.protocol.calibrate_instrument('A', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) self.protocol.run_all() self.protocol.run_all() def test_protocol_equality(self): # Set up a protocol. p1 = Protocol() p1.add_instrument('A', 'p200') p1.add_container('C1', 'tiprack.p200') p1.add_container('A1', 'microplate.96') p1.calibrate('A1', x=1, y=2, z=3) p1.calibrate_instrument('A', top=0, blowout=10) p1.transfer('A1:A1', 'A1:A2', ul=100) p1.transfer('A1:A2', 'A1:A3', ul=80) # And a copy. p2 = Protocol() p2.add_container('A1', 'microplate.96') p2.add_container('C1', 'tiprack.p200') p2.add_instrument('A', 'p200') p2.calibrate('A1', x=1, y=2, z=3) p2.calibrate_instrument('A', top=0, blowout=10) p2.transfer('A1:A1', 'A1:A2', ul=100) p2.transfer('A1:A2', 'A1:A3', ul=80) # They're identical. self.assertEqual(p1, p2) # Make a change. p2.add_instrument('B', 'p10') # No longer identical. self.assertNotEqual(p1, p2) def test_protocol_version(self): # Set up a protocol. self.protocol.add_instrument('A', 'p200') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.calibrate('A1', x=1, y=2, z=3) self.protocol.calibrate_instrument('A', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) # First version bump. v1 = self.protocol.bump_version() self.assertEqual(v1, '0.0.1') # No changes, version will stay the same. v2 = self.protocol.bump_version() self.assertEqual(v1, v2) # Make a change, bump the version. self.protocol.transfer('A1:A2', 'A1:A3', ul=80) v3 = self.protocol.bump_version() self.assertEqual(v3, '0.0.2') # Make a change, bump the version. self.protocol.transfer('A1:A1', 'A1:A1', ul=20) v4 = self.protocol.bump_version('feature') self.assertEqual(v4, '0.1.0') # Make a change, bump the version. self.protocol.transfer('A1:A1', 'A1:A1', ul=20) v5 = self.protocol.bump_version('major') self.assertEqual(v5, '1.0.0') def test_partial_protocol(self): p = Protocol.partial() p.transfer('A1:A1', 'A1:A3', ul=1) # As long as it doesn't throw an Exception, we're good. def test_partial_protocol_run(self): p = Protocol.partial() p.transfer('A1:A1', 'A1:A3', ul=1) with self.assertRaises(x.PartialProtocolException): # This shouldn't run because it's not valid. p.run_all() def test_valid_partial_protocol_run(self): p = Protocol.partial() p.add_instrument('A', 'p10') p.add_container("A1", "microplate.96") p.transfer('A1:A1', 'A1:A3', ul=1) # This should run because there are no Partial problems. p.run_all() def test_partial_protocol_export(self): p = Protocol.partial() p.transfer('A1:A1', 'A1:A3', ul=1) with self.assertRaises(x.PartialProtocolException): # This shouldn't export because it's not valid. p.export(JSONFormatter) def test_valid_partial_protocol_export(self): p = Protocol.partial() p.add_instrument('A', 'p10') p.add_container("A1", "microplate.96") p.transfer('A1:A1', 'A1:A3', ul=1) # This should export because there are no Partial problems. p.export(JSONFormatter) def test_protocol_addition_of_partial(self): p1 = Protocol() p1.add_instrument('A', 'p20') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol.partial() p2.add_container('A2', 'microplate.96') p2.transfer('A1:A1', 'A1:A3', ul=20) p2.transfer('A2:A1', 'A2:A4', ul=15) p3 = Protocol() p3.add_instrument('A', 'p20') p3.add_container('A1', 'microplate.96') p3.add_container('A2', 'microplate.96') p3.transfer('A1:A1', 'A1:A2', ul=10) p3.transfer('A1:A1', 'A1:A3', ul=20) p3.transfer('A2:A1', 'A2:A4', ul=15) self.assertEqual(p3, p1 + p2) def test_protocol_addition_of_partials(self): p1 = Protocol() p1.add_instrument('A', 'p20') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol.partial() p2.add_container('A2', 'microplate.96') p2.transfer('A1:A1', 'A1:A3', ul=20) p2.transfer('A2:A1', 'A2:A4', ul=15) p3 = Protocol.partial() p3.add_container('A3', 'microplate.96') p3.transfer('A1:A1', 'A1:A3', ul=20) p3.transfer('A1:A1', 'A3:A4', ul=15) p4 = Protocol() p4.add_instrument('A', 'p20') p4.add_container('A1', 'microplate.96') p4.add_container('A2', 'microplate.96') p4.add_container('A3', 'microplate.96') p4.transfer('A1:A1', 'A1:A2', ul=10) p4.transfer('A1:A1', 'A1:A3', ul=20) p4.transfer('A2:A1', 'A2:A4', ul=15) p4.transfer('A1:A1', 'A1:A3', ul=20) p4.transfer('A1:A1', 'A3:A4', ul=15) self.assertEqual(p4, p1 + p2 + p3) def test_protocol_added_to_partial(self): p1 = Protocol() p1.add_instrument('A', 'p20') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol.partial() p2.add_container('A2', 'microplate.96') p2.transfer('A1:A1', 'A1:A3', ul=20) p2.transfer('A2:A1', 'A2:A4', ul=15) with self.assertRaises(TypeError): p2 + p1 def test_protocol_addition(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A1', ul=10) p2 = Protocol() p2.add_instrument('A', 'p10') # Same definition; no conflict. p2.add_instrument('B', 'p20') # New instrument. p2.add_container('A1', 'microplate.96') # No conflict. p2.add_container('A2', 'microplate.96') # New container. p2.transfer('A1:A1', 'A1:A1', ul=12) p2.transfer('A1:A1', 'A2:A2', ul=20) p3 = Protocol() p3.add_instrument('A', 'p10') p3.add_instrument('B', 'p20') p3.add_container('A1', 'microplate.96') p3.add_container('A2', 'microplate.96') p3.transfer('A1:A1', 'A1:A1', ul=10) p3.transfer('A1:A1', 'A1:A1', ul=12) p3.transfer('A1:A1', 'A2:A2', ul=20) self.assertEqual(p3, p1 + p2) def test_protocol_label_addition(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96', label="Input") p1.transfer('A1:A1', 'A1:A1', ul=10) p2 = Protocol() p2.add_instrument('A', 'p10') # Same definition; no conflict. p2.add_instrument('B', 'p20') # New instrument. p2.add_container('A1', 'microplate.96', label="Input") # No conflict. p2.add_container('A2', 'microplate.96') # New container. p2.transfer('A1:A1', 'A1:A1', ul=9) p2.transfer('Input:A1', 'A2:A2', ul=20) p3 = Protocol() p3.add_instrument('A', 'p10') p3.add_instrument('B', 'p20') p3.add_container('A1', 'microplate.96', label="Input") p3.add_container('A2', 'microplate.96') p3.transfer('A1:A1', 'A1:A1', ul=10) p3.transfer('A1:A1', 'A1:A1', ul=9) p3.transfer('A1:A1', 'A2:A2', ul=20) self.assertEqual(p3, p1 + p2) def test_protocol_addition_label_conflict(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96', label="Input") p1.transfer('A1:A1', 'A1:A1', ul=10) p2 = Protocol() p2.add_instrument('A', 'p10') # Same definition; no conflict. p2.add_instrument('B', 'p20') # New instrument. p2.add_container('A1', 'microplate.96', label="Output") # Conflict. p2.add_container('A2', 'microplate.96') # New container. p2.transfer('A1:A1', 'A1:A1', ul=12) p2.transfer('Output:A1', 'A2:A2', ul=20) with self.assertRaises(x.ContainerConflict): p1 + p2 def test_protocol_addition_label_case(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96', label="Input") p2 = Protocol() p2.add_instrument('A', 'p10') p2.add_container('A1', 'microplate.96', label="INPUT") p3 = Protocol() p3.add_instrument('A', 'p10') p3.add_container('A1', 'microplate.96', label="INPUT") self.assertEqual((p1 + p2), p3) def test_protocol_addition_info(self): p1 = Protocol() p1.set_info(author="John Doe", name="Lorem Ipsum") p2 = Protocol() p2.set_info(author="Jane Doe") p3 = p1 + p2 self.assertEqual('Jane Doe', p3.info['author']) self.assertEqual('Lorem Ipsum', p3.info['name']) def test_protocol_addition_container_conflict(self): p1 = Protocol() p1.add_instrument('A', 'p10') p1.add_container('A1', 'microplate.96') p1.transfer('A1:A1', 'A1:A2', ul=10) p2 = Protocol() p2.add_container('A1', 'tiprack.p20') with self.assertRaises(x.ContainerConflict): p1 + p2
class ProtocolRequirementsTest(unittest.TestCase): def setUp(self): self.protocol = Protocol() def assertRequirements(self, expected, reqs=None): reqs = reqs or self.protocol.run_requirements ouch = [] extra = deepcopy(reqs) missing = deepcopy(expected) for ei, e in enumerate(expected): for ri, r in enumerate(reqs): try: self.assertEqual(e, r) except AssertionError: continue extra[ri] = None missing[ei] = None extra = [x for x in extra if x is not None] missing = [x for x in missing if x is not None] if len(extra) > 0: ouch.append("Found {} extra items: {}".format(len(extra), extra)) if len(missing) > 0: ouch.append("Missing items: {}".format(missing)) if len(ouch) > 0: assert False, "\n".join(ouch) def test_requirements_assertion(self): self.assertRequirements([{ 'foo': 'bar' }, { 'bizz': 'buzz' }], [{ 'foo': 'bar' }, { 'bizz': 'buzz' }]) with self.assertRaises(AssertionError): self.assertRequirements([{ 'foo': 'bar' }], [{ 'foo': 'bar' }, { 'bizz': 'buzz' }]) with self.assertRaises(AssertionError): self.assertRequirements([{ 'foo': 'bar' }, { 'bizz': 'buzz' }], [{ 'foo': 'bar' }]) def test_require_instrument_calibration(self): self.protocol.add_instrument('A', 'p20') self.protocol.add_container('A1', 'microplate') self.protocol.add_container('A2', 'tiprack.p20') self.protocol.transfer('A1:A1', 'A1:A2', ul=10) reqs = [{ 'type': 'calibrate_instrument', 'axis': 'A', 'instrument_name': 'p20' }, { 'type': 'calibrate_container', 'axis': 'A', 'address': (0, 0), 'container_name': 'microplate', 'instrument_name': 'p20' }, { 'type': 'calibrate_container', 'axis': 'A', 'address': (0, 1), 'container_name': 'tiprack.p20', 'instrument_name': 'p20' }] self.assertRequirements(reqs) self.protocol.calibrate_instrument(axis='A', top=10, bottom=10, blowout=10) reqs.pop(0) self.assertRequirements(reqs) self.protocol.calibrate('A1', axis='A') reqs.pop(0) self.assertRequirements(reqs) self.protocol.calibrate('A2', axis='A') reqs.pop(0) self.assertRequirements(reqs) def test_requirements_calibration_multiple_racks(self): self.protocol.add_instrument('A', 'p20') self.protocol.add_container('A1', 'microplate') self.protocol.add_container('A2', 'tiprack.p20') self.protocol.add_container('A3', 'tiprack.p20') for _ in range(50): self.protocol.transfer('A1:A1', 'A1:A2', ul=10) self.protocol.transfer('A1:A2', 'A1:A1', ul=10) reqs = [{ 'type': 'calibrate_instrument', 'axis': 'A', 'instrument_name': 'p20' }, { 'type': 'calibrate_container', 'axis': 'A', 'address': (0, 0), 'container_name': 'microplate', 'instrument_name': 'p20' }, { 'type': 'calibrate_container', 'axis': 'A', 'address': (0, 1), 'container_name': 'tiprack.p20', 'instrument_name': 'p20' }, { 'type': 'calibrate_container', 'axis': 'A', 'address': (0, 2), 'container_name': 'tiprack.p20', 'instrument_name': 'p20' }] self.assertRequirements(reqs) def test_no_errors_from_commands(self): self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('B1', 'tiprack.p200') self.protocol.add_container('C1', 'point.trash') self.protocol.calibrate('A1') self.protocol.calibrate('B1') self.protocol.calibrate('C1') self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13) self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer_group(('A1:A1', 'A1:A2', {'ul': 100})) self.protocol.consolidate('A1:A1', ('A1:A2', { 'ul': 100 }), ('A1:A2', { 'ul': 150 })) self.protocol.distribute('A1:A1', ('A1:A2', { 'ul': 100 }), ('A1:A2', { 'ul': 150 })) self.protocol.mix('A1:A2', ul=100, repetitions=5) self.protocol.run_requirements
class ProtocolTest(unittest.TestCase): def setUp(self): self.protocol = Protocol() @property def instructions(self): return self.protocol._commands def test_normalize_address(self): self.protocol.add_container('A1', 'microplate.96', label="Output") label = self.protocol._normalize_address('Output:A1') self.assertEqual(label, ('output', (0, 0))) slot = self.protocol._normalize_address('A1:A1') self.assertEqual(slot, ((0, 0), (0, 0))) def test_transfer(self): """ Basic transfer. """ self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('B1', 'microplate.96') self.protocol.add_instrument('A', 'p200') self.protocol.add_instrument('B', 'p20') self.protocol.transfer('A1:A1', 'B1:B1', ul=100, tool='p20') expected = [{ 'transfer': { 'tool': 'p20', 'volume': 100, 'start': ((0, 0), (0, 0)), 'end': ((1, 0), (1, 0)), 'blowout': True, 'touchtip': True } }] self.assertEqual(self.instructions, expected) def test_transfer_group(self): """ Transfer group. """ expected = [{ 'transfer_group': { 'tool': 'p10', 'transfers': [ { 'volume': 15, 'start': ((0, 0), (0, 0)), # A1:A1 'end': ((1, 0), (1, 0)), # B1:B1 'blowout': True, 'touchtip': True }, { 'volume': 1000, 'start': ((0, 1), (0, 1)), # A2:A2 'end': ((1, 1), (1, 1)), # B2:B2 'blowout': True, 'touchtip': True }, { 'volume': 12, 'start': ((0, 2), (0, 2)), # A3:A3 'end': ((1, 2), (1, 2)), # B3:B3 'blowout': False, 'touchtip': True }, { 'volume': 12, 'start': ((0, 3), (0, 3)), # A4:A4 'end': ((1, 3), (1, 3)), # B4:B4 'blowout': True, 'touchtip': True }, { 'volume': 12, 'start': ('label', (0, 4)), # label:A5 'end': ((1, 4), (2, 0)), # B5:C1 'blowout': True, 'touchtip': True } ] } }] self.protocol.add_container('A1', 'microplate.96', label="Label") self.protocol.transfer_group( ('A1:A1', 'B1:B1', {'ul': 15}), ('A2:A2', 'B2:B2', {'ml': 1}), ('A3:A3', 'B3:B3', {'blowout': False}), ('A4:A4', 'B4:B4'), ('Label:A5', 'B5:C1'), ul=12, tool='p10' ) self.assertEqual(self.instructions, expected) def test_distribute(self): self.protocol.distribute( 'A1:A1', ('B1:B1', 50), ('C1:C1', 5), ('D1:D1', 10) ) expected = [{ 'distribute': { 'tool': 'p10', 'blowout': True, 'start': ((0, 0), (0, 0)), 'transfers': [ { 'volume': 50, 'end': ((1, 0), (1, 0)), # B1:B1 }, { 'volume': 5, 'end': ((2, 0), (2, 0)), # C1:C1 }, { 'volume': 10, 'end': ((3, 0), (3, 0)) # D1:D1 } ] } }] self.assertEqual(self.instructions, expected) def test_consolidate(self): """ Consolidate. """ self.protocol.consolidate( 'A1:A1', ('B1:B1', 50), ('C1:C1', 5), ('D1:D1', 10) ) expected = [{ 'consolidate': { 'tool': 'p10', 'blowout': True, 'end': ((0, 0), (0, 0)), 'transfers': [ { 'volume': 50, 'start': ((1, 0), (1, 0)), # B1:B1 }, { 'volume': 5, 'start': ((2, 0), (2, 0)), # C1:C1 }, { 'volume': 10, 'start': ((3, 0), (3, 0)) # D1:D1 } ] } }] self.assertEqual(self.instructions, expected) def test_mix(self): """ Mix. """ self.protocol.mix( 'A1:A1', volume=50, repetitions=10 ) expected = [{'mix': { 'tool': 'p10', 'start': ((0, 0), (0, 0)), # A1:A1 'blowout': True, 'volume': 50, 'reps': 10 }}] self.assertEqual(self.instructions, expected) def test_protocol_run_twice(self): """ Run a protocol twice without error. """ self.protocol.add_instrument('A', 'p200') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.calibrate('A1', x=1, y=2, z=3) self.protocol.calibrate_instrument('A', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) self.protocol.run_all() self.protocol.run_all()
class ProtocolRequirementsTest(unittest.TestCase): def setUp(self): self.protocol = Protocol() def assertRequirements(self, expected, reqs=None): reqs = reqs or self.protocol.run_requirements ouch = [] extra = deepcopy(reqs) missing = deepcopy(expected) for ei, e in enumerate(expected): for ri, r in enumerate(reqs): try: self.assertEqual(e, r) except AssertionError: continue extra[ri] = None missing[ei] = None extra = [x for x in extra if x is not None] missing = [x for x in missing if x is not None] if len(extra) > 0: ouch.append("Found {} extra items: {}".format(len(extra), extra)) if len(missing) > 0: ouch.append("Missing items: {}".format(missing)) if len(ouch) > 0: assert False, "\n".join(ouch) def test_requirements_assertion(self): self.assertRequirements( [{'foo': 'bar'}, {'bizz': 'buzz'}], [{'foo': 'bar'}, {'bizz': 'buzz'}] ) with self.assertRaises(AssertionError): self.assertRequirements( [{'foo': 'bar'}], [{'foo': 'bar'}, {'bizz': 'buzz'}] ) with self.assertRaises(AssertionError): self.assertRequirements( [{'foo': 'bar'}, {'bizz': 'buzz'}], [{'foo': 'bar'}] ) def test_require_instrument_calibration(self): self.protocol.add_instrument('A', 'p20') self.protocol.add_container('A1', 'microplate') self.protocol.add_container('A2', 'tiprack.p20') self.protocol.transfer('A1:A1', 'A1:A2', ul=10) reqs = [ {'type': 'calibrate_instrument', 'axis': 'A', 'instrument_name': 'p20'}, {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 0), 'container_name': 'microplate', 'instrument_name': 'p20'}, {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 1), 'container_name': 'tiprack.p20', 'instrument_name': 'p20'} ] self.assertRequirements(reqs) self.protocol.calibrate_instrument(axis='A', top=10, bottom=10, blowout=10) reqs.pop(0) self.assertRequirements(reqs) self.protocol.calibrate('A1', axis='A') reqs.pop(0) self.assertRequirements(reqs) self.protocol.calibrate('A2', axis='A') reqs.pop(0) self.assertRequirements(reqs) def test_requirements_calibration_multiple_racks(self): self.protocol.add_instrument('A', 'p20') self.protocol.add_container('A1', 'microplate') self.protocol.add_container('A2', 'tiprack.p20') self.protocol.add_container('A3', 'tiprack.p20') for _ in range(50): self.protocol.transfer('A1:A1', 'A1:A2', ul=10) self.protocol.transfer('A1:A2', 'A1:A1', ul=10) reqs = [ {'type': 'calibrate_instrument', 'axis': 'A', 'instrument_name': 'p20'}, {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 0), 'container_name': 'microplate', 'instrument_name': 'p20'}, {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 1), 'container_name': 'tiprack.p20', 'instrument_name': 'p20'}, {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 2), 'container_name': 'tiprack.p20', 'instrument_name': 'p20'} ] self.assertRequirements(reqs) def test_no_errors_from_commands(self): self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('B1', 'tiprack.p200') self.protocol.add_container('C1', 'point.trash') self.protocol.calibrate('A1') self.protocol.calibrate('B1') self.protocol.calibrate('C1') self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13) self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer_group( ('A1:A1', 'A1:A2', {'ul': 100}) ) self.protocol.consolidate( 'A1:A1', ('A1:A2', {'ul':100}), ('A1:A2', {'ul':150}) ) self.protocol.distribute( 'A1:A1', ('A1:A2', {'ul':100}), ('A1:A2', {'ul':150}) ) self.protocol.mix('A1:A2', ul=100, repetitions=5) self.protocol.run_requirements
from flask_socketio import SocketIO, emit import logging import math protocol = Protocol() motor_handler = protocol.attach_motor() protocol.add_instrument('B', 'p200') protocol.add_container('A1', 'microplate.96') protocol.add_container('C1', 'tiprack.p200') protocol.add_container('B2', 'point.trash') protocol.calibrate('A1', x=1, y=2, top=3, bottom=13) protocol.calibrate('A1:A2', bottom=5) protocol.calibrate('C1', x=100, y=100, top=50) protocol.calibrate('B2', x=200, y=250, top=15) protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25) protocol.transfer('A1:A1', 'A1:A2', ul=100) protocol.transfer('A1:A2', 'A1:A3', ul=80) logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%d-%m-%y %H:%M:%S' ) app = Flask(__name__) socketio = SocketIO(app, async_mode='gevent') @app.route('/') def index():
class MotorHandlerTest(unittest.TestCase): def setUp(self): self.protocol = Protocol() def test_basic_transfer(self): """ Basic transfer. """ motor = self.protocol.attach_motor() output_log = motor._driver self.protocol.add_instrument('A', 'p20') self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.add_container('B1', 'tiprack.p20') self.protocol.add_container('B2', 'point.trash') self.protocol.calibrate('A1', axis="A", x=1, y=2, top=3, bottom=10) self.protocol.calibrate('B1', axis="A", x=4, y=5, top=6, bottom=15) self.protocol.calibrate('B2', axis="A", x=50, y=60, top=70) self.protocol.calibrate('A1:A2', axis="B", bottom=5) self.protocol.calibrate('C1', axis="B", x=100, y=100, top=50) self.protocol.calibrate('B2', axis="B", x=200, y=250, top=15) self.protocol.calibrate('A1', axis="B", x=1, y=2, top=3, bottom=13) self.protocol.calibrate_instrument('A', top=0, blowout=1, droptip=2) self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25) self.protocol.transfer('A1:A1', 'A1:A2', ul=10) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) prog_out = [] for progress in self.protocol.run(): prog_out.append(progress) expected = [ # Transfer 1. {'x': 4, 'y': 5}, # Pickup tip. {'z': 6}, {'z': 0}, # Move to well. {'x': 1, 'y': 2}, {'z': 3}, {'a': 0.5}, # Plunge. {'x': 1, 'y': 2}, {'z': 10}, # Move into well. {'a': 0}, # Release. {'z': 0}, # Move up. {'x': 1, 'y': 11}, # Move to well. {'z': 3}, {'x': 1, 'y': 11}, {'z': 10}, # Move into well. {'a': 1}, # Blowout. {'z': 0}, # Move up. {'a': 0}, # Release. {'x': 50, 'y': 60}, # Dispose tip. {'z': 70}, {'a': 2}, {'a': 0}, # Transfer 2. {'x': 100, 'y': 100}, {'z': 50}, {'z': 0}, {'x': 1, 'y': 11}, {'z': 3}, {'b': 4.0}, {'x': 1, 'y': 11}, {'z': 5}, {'b': 0}, {'z': 0}, {'x': 1, 'y': 20}, {'z': 3}, {'x': 1, 'y': 20}, {'z': 13}, {'b': 10}, {'z': 0}, {'b': 0}, {'x': 200, 'y': 250}, {'z': 15}, {'b': 25}, {'b': 0} ] self.assertEqual(expected, output_log.movements) self.assertEqual([(0, 2), (1, 2), (2, 2)], prog_out) def test_calibrate_without_axis(self): self.protocol.add_instrument('A', 'p20') self.protocol.add_instrument('B', 'p200') with self.assertRaises(x.DataMissing): self.protocol.calibrate('A1', top=0, bottom=0) def test_transfer_without_tiprack(self): """ Raise error when no tiprack found. """ self.protocol.attach_motor() self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.calibrate('A1', top=0, bottom=0) self.protocol.calibrate_instrument('B', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) with self.assertRaises(x.ContainerMissing): self.protocol.run_all() def test_transfer_without_dispose_point(self): """ Raise when no dispose point set. """ self.protocol.attach_motor() self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.calibrate('A1') self.protocol.calibrate('C1') self.protocol.calibrate_instrument('B', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) with self.assertRaises(x.ContainerMissing): self.protocol.run_all() def test_instrument_missing(self): with self.assertRaises(x.InstrumentMissing): m = self.protocol.attach_motor() m.get_pipette(has_volume=1000) def test_no_errors_from_commands(self): self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('B1', 'tiprack.p200') self.protocol.add_container('C1', 'point.trash') self.protocol.calibrate('A1') self.protocol.calibrate('B1') self.protocol.calibrate('C1') self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13) self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer_group( ('A1:A1', 'A1:A2', {'ul': 100}) ) self.protocol.consolidate( 'A1:A1', ('A1:A2', {'ul':100}), ('A1:A2', {'ul':150}) ) self.protocol.distribute( 'A1:A1', ('A1:A2', {'ul':100}), ('A1:A2', {'ul':150}) ) self.protocol.mix('A1:A2', ul=100, repetitions=5) motor = self.protocol.attach_motor() output_log = motor._driver movements = output_log.movements # We're not really testing anything except that it runs without # errors. self.protocol.run_all()
class MotorHandlerTest(unittest.TestCase): def setUp(self): self.protocol = Protocol() def test_basic_transfer(self): """ Basic transfer. """ motor = self.protocol.attach_motor() output_log = motor._driver self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.add_container('B2', 'point.trash') self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13) self.protocol.calibrate('A1:A2', bottom=5) self.protocol.calibrate('C1', x=100, y=100, top=50) self.protocol.calibrate('B2', x=200, y=250, top=15) self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) prog_out = [] for progress in self.protocol.run(): prog_out.append(progress) expected = [ # Transfer 1. {'x': 100, 'y': 100}, # Pickup tip. {'z': 50}, {'z': 0}, # Move to well. {'x': 1, 'y': 2}, {'z': 3}, {'b': 5.0}, # Plunge. {'x': 1, 'y': 2}, {'z': 13}, # Move into well. {'b': 0}, # Release. {'z': 0}, # Move up. {'x': 1, 'y': 11}, # Move to well. {'z': 3}, {'x': 1, 'y': 11}, {'z': 5}, # Move into well. {'b': 10}, # Blowout. {'z': 0}, # Move up. {'b': 0}, # Release. {'x': 200, 'y': 250}, # Dispose tip. {'z': 15}, {'b': 25}, {'b': 0}, # Transfer 2. {'x': 100, 'y': 109}, {'z': 50}, {'z': 0}, {'x': 1, 'y': 11}, {'z': 3}, {'b': 4.0}, {'x': 1, 'y': 11}, {'z': 5}, {'b': 0}, {'z': 0}, {'x': 1, 'y': 20}, {'z': 3}, {'x': 1, 'y': 20}, {'z': 13}, {'b': 10}, {'z': 0}, {'b': 0}, {'x': 200, 'y': 250}, {'z': 15}, {'b': 25}, {'b': 0} ] self.assertEqual(expected, output_log.movements) self.assertEqual([(0, 2), (1, 2), (2, 2)], prog_out) def test_transfer_without_tiprack(self): """ Raise error when no tiprack found. """ self.protocol.attach_motor() self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.calibrate_instrument('B', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) with self.assertRaises(KeyError): for progress in self.protocol.run(): continue def test_transfer_without_dispose_point(self): """ Raise when no dispose point set. """ self.protocol.attach_motor() self.protocol.add_instrument('B', 'p200') self.protocol.add_container('A1', 'microplate.96') self.protocol.add_container('C1', 'tiprack.p200') self.protocol.calibrate_instrument('B', top=0, blowout=10) self.protocol.transfer('A1:A1', 'A1:A2', ul=100) self.protocol.transfer('A1:A2', 'A1:A3', ul=80) with self.assertRaises(KeyError): for progress in self.protocol.run(): continue