Esempio n. 1
0
def test_all_pipette_models_can_transfer():
    from opentrons.config import pipette_config

    models = [
        'p10_single', 'p10_multi', 'p50_single', 'p50_multi',
        'p300_single', 'p300_multi', 'p1000_single'
    ]

    for m in models:
        robot.reset()
        v1 = m + '_v1'
        v13 = m + '_v1.3'
        left = instruments._create_pipette_from_config(
            config=pipette_config.load(v1),
            mount='left',
            name=v1)
        right = instruments._create_pipette_from_config(
            config=pipette_config.load(v13),
            mount='right',
            name=v13)

        left.tip_attached = True
        right.tip_attached = True
        left.aspirate().dispense()
        right.aspirate().dispense()
Esempio n. 2
0
def test_execute_function_apiv2(protocol,
                                protocol_file,
                                monkeypatch,
                                virtual_smoothie_env,
                                mock_get_attached_instr):

    mock_get_attached_instr.return_value[types.Mount.LEFT]\
        = {'config': load('p10_single_v1.5'), 'id': 'testid'}
    mock_get_attached_instr.return_value[types.Mount.RIGHT]\
        = {'config': load('p300_single_v1.5'), 'id': 'testid2'}
    entries = []

    def emit_runlog(entry):
        nonlocal entries
        entries.append(entry)

    execute.execute(
        protocol.filelike, 'testosaur_v2.py', emit_runlog=emit_runlog)
    assert [item['payload']['text'] for item in entries
            if item['$'] == 'before'] == [
        'Picking up tip from A1 of Opentrons 96 Tip Rack 300 µL on 1',
        'Aspirating 10.0 uL from A1 of Corning 96 Well Plate 360 µL Flat on 2 at 150.0 uL/sec',  # noqa(E501),
        'Dispensing 10.0 uL into B1 of Corning 96 Well Plate 360 µL Flat on 2 at 300.0 uL/sec',  # noqa(E501),
        'Dropping tip into H12 of Opentrons 96 Tip Rack 300 µL on 1'
        ]
Esempio n. 3
0
def test_override_load(config_tempdir):
    cdir = CONFIG['pipette_config_overrides_dir']

    existing_overrides = {
        'pickUpCurrent': {
            'value': 1231.213
        },
        'dropTipSpeed': {
            'value': 121
        },
        'quirks': {
            'dropTipShake': True
        }
    }

    existing_id = 'ohoahflaseh08102qa'
    with (cdir / f'{existing_id}.json').open('w') as ovf:
        json.dump(existing_overrides, ovf)

    pconf = pipette_config.load('p300_multi_v1.4', existing_id)

    assert pconf.pick_up_current == \
        existing_overrides['pickUpCurrent']['value']
    assert pconf.drop_tip_speed == existing_overrides['dropTipSpeed']['value']
    assert pconf.quirks == ['dropTipShake']

    new_id = '0djaisoa921jas'
    new_pconf = pipette_config.load('p300_multi_v1.4', new_id)

    assert new_pconf != pconf

    unspecced = pipette_config.load('p300_multi_v1.4')
    assert unspecced == new_pconf
Esempio n. 4
0
def test_override_save(config_tempdir):
    cdir = CONFIG['pipette_config_overrides_dir']

    overrides = {
        'pickUpCurrent': {
            'value': 1231.213
        },
        'dropTipSpeed': {
            'value': 121
        },
        'dropTipShake': {
            'value': False
        }
    }

    new_id = 'aoa2109j09cj2a'
    model = 'p300_multi_v1'

    old_pconf = pipette_config.load('p300_multi_v1.4', new_id)

    assert old_pconf.quirks == ['dropTipShake']

    pipette_config.save_overrides(new_id, overrides, model)

    assert (cdir / f'{new_id}.json').is_file()

    loaded = pipette_config.load_overrides(new_id)

    assert loaded['pickUpCurrent']['value'] == \
        overrides['pickUpCurrent']['value']
    assert loaded['dropTipSpeed']['value'] == \
        overrides['dropTipSpeed']['value']

    new_pconf = pipette_config.load('p300_multi_v1.4', new_id)
    assert new_pconf.quirks == []
Esempio n. 5
0
def test_pipette_version_1_0_and_1_3_extended_travel():
    models = [
        'p10_single', 'p10_multi', 'p50_single', 'p50_multi',
        'p300_single', 'p300_multi', 'p1000_single'
    ]

    for m in models:
        robot.reset()
        v1 = m + '_v1'
        v13 = m + '_v1.3'
        left = instruments._create_pipette_from_config(
            config=pipette_config.load(v1),
            mount='left',
            name=v1)
        right = instruments._create_pipette_from_config(
            config=pipette_config.load(v13),
            mount='right',
            name=v13)

        # the difference between v1 and v1.3 is that the plunger's travel
        # distance extended, allowing greater ranges for aspirate/dispense
        # and blow-out. Test that all v1.3 pipette have larger travel thant v1
        left_poses = left.plunger_positions
        left_diff = left_poses['top'] - left_poses['blow_out']
        right_poses = right.plunger_positions
        right_diff = right_poses['top'] - right_poses['blow_out']
        assert right_diff > left_diff
Esempio n. 6
0
    def get_attached_pipettes(self):
        """
        Gets model names of attached pipettes

        :return: :dict with keys 'left' and 'right' and a model string for each
            mount, or 'uncommissioned' if no model string available
        """
        left_data = {
            'mount_axis': 'z',
            'plunger_axis': 'b',
            'model': self.model_by_mount['left']['model'],
            'name': self.model_by_mount['left']['name'],
            'id': self.model_by_mount['left']['id']
        }
        left_model = left_data.get('model')

        if left_model:
            tip_length = pipette_config.load(left_model,
                                             left_data['id']).tip_length
            left_data.update({'tip_length': tip_length})

        right_data = {
            'mount_axis': 'a',
            'plunger_axis': 'c',
            'model': self.model_by_mount['right']['model'],
            'name': self.model_by_mount['right']['name'],
            'id': self.model_by_mount['right']['id']
        }
        right_model = right_data.get('model')
        if right_model:
            tip_length = pipette_config.load(right_model,
                                             right_data['id']).tip_length
            right_data.update({'tip_length': tip_length})
        return {'left': left_data, 'right': right_data}
Esempio n. 7
0
 def fake_gai(expected):
     return {
         Mount.LEFT: {
             'config': pc.load(model2[0]),
             'id': 'fakeid'
         },
         Mount.RIGHT: {
             'config': pc.load(model2[0]),
             'id': 'fakeid2'
         }
     }
Esempio n. 8
0
 def __init__(self,
              model: 'PipetteModel',
              inst_offset_config: Dict[str, Tuple[float, float, float]],
              pipette_id: str = None) -> None:
     self._config = pipette_config.load(model, pipette_id)
     self._name = name_for_model(model)
     self._model = model
     self._model_offset = self._config.model_offset
     self._current_volume = 0.0
     self._working_volume = self._config.max_volume
     self._current_tip_length = 0.0
     self._current_tiprack_diameter = 0.0
     self._fallback_tip_length = self._config.tip_length
     self._tip_overlap_map = self._config.tip_overlap
     self._has_tip = False
     self._pipette_id = pipette_id
     pip_type = 'multi' if self._config.channels == 8 else 'single'
     self._instrument_offset = Point(*inst_offset_config[pip_type])
     self._log = mod_log.getChild(
         self._pipette_id if self._pipette_id else '<unknown>')
     self._log.info("loaded: {}, instr offset {}".format(
         model, self._instrument_offset))
     self.ready_to_aspirate = False
     #: True if ready to aspirate
     self._aspirate_flow_rate\
         = self._config.default_aspirate_flow_rates['2.0']
     self._dispense_flow_rate\
         = self._config.default_dispense_flow_rates['2.0']
     self._blow_out_flow_rate\
         = self._config.default_blow_out_flow_rates['2.0']
Esempio n. 9
0
def test_shake_during_pick_up(monkeypatch, robot, instruments):
    robot.reset()
    pip = instruments._create_pipette_from_config(
        config=pipette_config.load('p1000_single_v2.0'),
        mount='left',
        name='p1000_single_v2.0')
    tiprack = containers_load(robot, 'opentrons_96_tiprack_1000ul', '1')

    shake_tips_pick_up = mock.Mock(side_effect=pip._shake_off_tips_pick_up)
    monkeypatch.setattr(pip, '_shake_off_tips_pick_up', shake_tips_pick_up)

    # Test double shake for after pick up tips
    pip.pick_up_tip(tiprack[0])
    assert shake_tips_pick_up.call_count == 2

    actual_calls = []

    def mock_jog(pose_tree, axis, distance):
        actual_calls.append((axis, distance))

    monkeypatch.setattr(pip, '_jog', mock_jog)

    # Test shake in both x and y
    shake_tips_pick_up()
    expected_calls = [('x', -0.3), ('x', 0.6), ('x', -0.3), ('y', -0.3),
                      ('y', 0.6), ('y', -0.3), ('z', 20)]
    assert actual_calls == expected_calls
    pip.tip_attached = False
Esempio n. 10
0
    def __init__(self, config: TopConfigurationContext,
                 existingInstance: Pipette):
        super(EnhancedPipetteV1, self).__init__(config)

        # load the config (again) in order to extract some more data later
        from opentrons.config import pipette_config
        from opentrons.config.pipette_config import configs
        pipette_model_version, pip_id = instruments._pipette_details(
            self.mount, self.name)
        self.pipette_config = pipette_config.load(pipette_model_version,
                                                  pip_id)
        if not hasattr(self.pipette_config, 'drop_tip_min'):  # future-proof
            cfg = configs[
                pipette_model_version]  # ignores the id-based overrides done by pipette_config.load, but we can live with that
            self.pipette_config_drop_tip_min = cfg['dropTip'][
                'min']  # hack: can't add field to pipette_config, so we do it this way
        else:
            self.pipette_config_drop_tip_min = self.pipette_config.drop_tip_min

        # try to mitigate effects of static electricity on small pipettes: they can cling to the tip on drop, causing disasters when next tips are picked up
        if self.config.enable_enhancements and self.name == 'p10_single':
            # dropping twice probably will help
            # if 'doubleDropTip' not in self.quirks:  # not necessary, in the end, it seems
            #     self.quirks.append('doubleDropTip')
            # plunging lower also helps, clearly
            self.plunger_positions[
                'drop_tip'] = self.pipette_config_drop_tip_min
Esempio n. 11
0
async def test_get_pipettes(async_server, async_client, monkeypatch):
    test_model = 'p300_multi_v1'
    test_name = 'p300_multi'
    test_id = '123abc'

    hw = async_server['com.opentrons.hardware']
    hw._backend._attached_instruments = {
        types.Mount.RIGHT: {'model': test_model, 'id': test_id},
        types.Mount.LEFT: {'model': test_model, 'id': test_id}
    }

    model = pipette_config.load(test_model)
    expected = {
        'left': {
            'model': test_model,
            'name': test_name,
            'tip_length': model.tip_length,
            'mount_axis': 'z',
            'plunger_axis': 'b',
            'id': test_id
        },
        'right': {
            'model': test_model,
            'name': test_name,
            'tip_length': model.tip_length,
            'mount_axis': 'a',
            'plunger_axis': 'c',
            'id': test_id
        }
    }

    resp = await async_client.get('/pipettes?refresh=true')
    text = await resp.text()
    assert resp.status == 200
    assert json.loads(text) == expected
Esempio n. 12
0
    def pipette_by_name(
            self,
            mount,
            name_or_model,
            trash_container='',
            tip_racks=[],
            aspirate_flow_rate=None,
            dispense_flow_rate=None,
            min_volume=None,
            max_volume=None,
            blow_out_flow_rate=None):
        pipette_model_version, pip_id = self._pipette_details(
            mount, name_or_model)
        config = pipette_config.load(pipette_model_version, pip_id)

        if pip_id and config.backcompat_name == name_or_model:
            log.warning(
                f"Using a deprecated constructor for {pipette_model_version}")
            constructor_config = pipette_config.name_config()[name_or_model]
            config = config._replace(
                min_volume=constructor_config['minVolume'],
                max_volume=constructor_config['maxVolume'])
            name_or_model = config.name
        return self._create_pipette_from_config(
            config=config,
            mount=mount,
            name=name_or_model,
            model=pipette_model_version,
            trash_container=trash_container,
            tip_racks=tip_racks,
            aspirate_flow_rate=aspirate_flow_rate,
            dispense_flow_rate=dispense_flow_rate,
            min_volume=min_volume,
            max_volume=max_volume,
            blow_out_flow_rate=blow_out_flow_rate)
Esempio n. 13
0
async def save_z(data):
    """
    Save the current Z height value for the calibration data

    :param data: Information obtained from a POST request.
    The content type is application/json.
    The correct packet form should be as follows:
    {
      'token': UUID token from current session start
      'command': 'save z'
    }
    """
    if not session.tip_length:
        message = "Tip length must be set before calibrating"
        status = 400
    else:
        if not feature_flags.use_protocol_api_v2():
            mount = 'Z' if session.current_mount == 'left' else 'A'
            actual_z = position(mount, session.adapter)[-1]
            length_offset = pipette_config.load(
                session.current_model, session.pipette_id).model_offset[-1]
            session.z_value = actual_z - session.tip_length + length_offset
        else:
            session.z_value = position(session.current_mount, session.adapter,
                                       session.cp)[-1]

        session.current_transform[2][3] = session.z_value

        session.adapter.update_config(gantry_calibration=list(
            map(lambda i: list(i), session.current_transform)))

        message = "Saved z: {}".format(session.z_value)
        status = 200
    return web.json_response({'message': message}, status=status)
Esempio n. 14
0
def mock_hw(hardware):
    pip = pipette.Pipette(load("p300_single_v2.1", 'testId'), {
        'single': [0, 0, 0],
        'multi': [0, 0, 0]
    }, 'testId')
    hardware._attached_instruments = {Mount.RIGHT: pip}
    hardware._current_pos = Point(0, 0, 0)

    async def async_mock(*args, **kwargs):
        pass

    async def async_mock_move_rel(*args, **kwargs):
        delta = kwargs.get('delta', Point(0, 0, 0))
        hardware._current_pos += delta

    async def async_mock_move_to(*args, **kwargs):
        to_pt = kwargs.get('abs_position', Point(0, 0, 0))
        hardware._current_pos = to_pt

    async def gantry_pos_mock(*args, **kwargs):
        return hardware._current_pos

    hardware.move_rel = MagicMock(side_effect=async_mock_move_rel)
    hardware.pick_up_tip = MagicMock(side_effect=async_mock)
    hardware.drop_tip = MagicMock(side_effect=async_mock)
    hardware.gantry_position = MagicMock(side_effect=gantry_pos_mock)
    hardware.move_to = MagicMock(side_effect=async_mock_move_to)
    hardware.get_instrument_max_height.return_value = 180
    return hardware
Esempio n. 15
0
    def _query_mount(
        self, mount: Mount, expected: Union[PipetteModel, PipetteName,
                                            None]) -> AttachedInstrument:
        found_model: Optional[PipetteModel]\
                = self._smoothie_driver.read_pipette_model(  # type: ignore
                    mount.name.lower())
        if found_model and found_model not in pipette_config.config_models:
            # TODO: Consider how to handle this error - it bubbles up now
            # and will cause problems at higher levels
            MODULE_LOG.error(f'Bad model on {mount.name}: {found_model}')
            found_model = None
        found_id = self._smoothie_driver.read_pipette_id(mount.name.lower())

        if found_model:
            config = pipette_config.load(found_model, found_id)
            if expected:
                acceptable = [config.name] + config.back_compat_names
                if expected not in acceptable:
                    raise RuntimeError(f'mount {mount}: instrument'
                                       f' {expected} was requested'
                                       f' but {config.model} is present')
            return {'config': config, 'id': found_id}
        else:
            if expected:
                raise RuntimeError(f'mount {mount}: instrument {expected} was'
                                   f' requested, but no instrument is present')
            return {'config': None, 'id': None}
Esempio n. 16
0
def test_volume_tracking():
    for config in pipette_config.config_models:
        loaded = pipette_config.load(config)
        pip = pipette.Pipette(config, {
            'single': [0, 0, 0],
            'multi': [0, 0, 0]
        }, 'testID')
        assert pip.current_volume == 0.0
        assert pip.available_volume == loaded.max_volume
        assert pip.ok_to_add_volume(loaded.max_volume - 0.1)
        pip.set_current_volume(0.1)
        with pytest.raises(AssertionError):
            pip.set_current_volume(loaded.max_volume + 0.1)
        with pytest.raises(AssertionError):
            pip.set_current_volume(-1)
        assert pip.current_volume == 0.1
        pip.remove_current_volume(0.1)
        with pytest.raises(AssertionError):
            pip.remove_current_volume(0.1)
        assert pip.current_volume == 0.0
        pip.set_current_volume(loaded.max_volume)
        assert not pip.ok_to_add_volume(0.1)
        with pytest.raises(AssertionError):
            pip.add_current_volume(0.1)
        assert pip.current_volume == loaded.max_volume
Esempio n. 17
0
def test_flow_rate_setting():
    pip = pipette.Pipette(pipette_config.load('p300_single_v2.0'), {
        'single': [0, 0, 0],
        'multi': [0, 0, 0]
    }, 'testId')
    # pipettes should load settings from config at init time
    assert pip.aspirate_flow_rate\
        == pip.config.default_aspirate_flow_rates['2.0']
    assert pip.dispense_flow_rate\
        == pip.config.default_dispense_flow_rates['2.0']
    assert pip.blow_out_flow_rate\
        == pip.config.default_blow_out_flow_rates['2.0']
    # changing flow rates with normal property access shouldn't touch
    # config or other flow rates
    config = pip.config
    pip.aspirate_flow_rate = 2
    assert pip.aspirate_flow_rate == 2
    assert pip.dispense_flow_rate\
        == pip.config.default_dispense_flow_rates['2.0']
    assert pip.blow_out_flow_rate\
        == pip.config.default_blow_out_flow_rates['2.0']
    assert pip.config is config
    pip.dispense_flow_rate = 3
    assert pip.aspirate_flow_rate == 2
    assert pip.dispense_flow_rate == 3
    assert pip.blow_out_flow_rate\
        == pip.config.default_blow_out_flow_rates['2.0']
    assert pip.config is config
    pip.blow_out_flow_rate = 4
    assert pip.aspirate_flow_rate == 2
    assert pip.dispense_flow_rate == 3
    assert pip.blow_out_flow_rate == 4
    assert pip.config is config
Esempio n. 18
0
    def cache_instrument_models(self):
        """
        Queries Smoothie for the model and ID strings of attached pipettes, and
        saves them so they can be reported without querying Smoothie again (as
        this could interrupt a command if done during a run or other movement).

        Shape of return dict should be:

        ```
        {
          "left": {
            "model": "<model_string>" or None,
            "id": "<pipette_id_string>" or None
          },
          "right": {
            "model": "<model_string>" or None,
            "id": "<pipette_id_string>" or None
          }
        }
        ```

        :return: a dict with pipette data (shape described above)
        """
        log.debug("Updating instrument model cache")
        for mount in self.model_by_mount.keys():
            model_value = self._driver.read_pipette_model(mount)
            if model_value:
                name_value = pipette_config.name_for_model(model_value)
            else:
                name_value = None
            plunger_axis = 'B' if mount == 'left' else 'C'
            mount_axis = 'Z' if mount == 'left' else 'A'
            if model_value:
                cfg = pipette_config.load(model_value)
                home_pos = cfg.home_position
                max_travel = cfg.max_travel
                steps_mm = cfg.steps_per_mm
            else:
                home_pos = self.config.default_pipette_configs['homePosition']
                max_travel = self.config.default_pipette_configs['maxTravel']
                steps_mm = self.config.default_pipette_configs['stepsPerMM']

            self._driver.update_steps_per_mm({plunger_axis: steps_mm})
            self._driver.update_pipette_config(mount_axis, {'home': home_pos})
            self._driver.update_pipette_config(plunger_axis,
                                               {'max_travel': max_travel})

            if model_value:
                id_response = self._driver.read_pipette_id(mount)
            else:
                id_response = None
            self.model_by_mount[mount] = {
                'model': model_value,
                'id': id_response,
                'name': name_value
            }
            log.debug("{}: {} [{}]".format(mount,
                                           self.model_by_mount[mount]['model'],
                                           self.model_by_mount[mount]['id']))
Esempio n. 19
0
def mock_hw_pipette_all_combos(request):
    model = request.param
    return pipette.Pipette(load(model, 'testId'),
                           {
                               'single': [0, 0, 0],
                               'multi': [0, 0, 0]
                           },
                           'testId')
Esempio n. 20
0
def test_user_flow_select_pipette(pipettes, target_mount, hardware):
    pip, pip2 = None, None
    if pipettes[0]:
        pip = pipette.Pipette(load(pipettes[0], 'testId'), {
            'single': [0, 0, 0],
            'multi': [0, 0, 0]
        }, 'testId')
    if pipettes[1]:
        pip2 = pipette.Pipette(load(pipettes[1], 'testId'), {
            'single': [0, 0, 0],
            'multi': [0, 0, 0]
        }, 'testId2')
    hardware._attached_instruments = {Mount.LEFT: pip, Mount.RIGHT: pip2}

    uf = DeckCalibrationUserFlow(hardware=hardware)
    assert uf._hw_pipette == \
        hardware._attached_instruments[target_mount]
Esempio n. 21
0
 def fake_attached(stuff):
     return {
         mount: {
             'config': pc.load(value['model']),
             'id': value['id']
         }
         for mount, value in dummy_instruments.items()
     }
Esempio n. 22
0
def test_tip_overlap(config_model):
    loaded = pipette_config.load(config_model)
    pip = pipette.Pipette(loaded, {
        'single': [0, 0, 0],
        'multi': [0, 0, 0]
    }, 'testId')
    assert pip.config.tip_overlap\
        == pipette_config.configs[config_model]['tipOverlap']
Esempio n. 23
0
 def _attached_to_mount(
         self, mount: types.Mount,
         expected_instr: Optional[PipetteName]) -> AttachedInstrument:
     init_instr = self._attached_instruments.get(mount, {
         'model': None,
         'id': None
     })
     found_model = init_instr['model']
     back_compat: List['PipetteName'] = []
     if found_model:
         back_compat = configs[found_model].get('backCompatNames', [])
     if expected_instr and found_model\
             and (not found_model.startswith(expected_instr)
                  and expected_instr not in back_compat):
         if self._strict_attached:
             raise RuntimeError(
                 'mount {}: expected instrument {} but got {}'.format(
                     mount.name, expected_instr, found_model))
         else:
             return {
                 'config': load(dummy_model_for_name(expected_instr)),
                 'id': None
             }
     elif found_model and expected_instr:
         # Instrument detected matches instrument expected (note:
         # "instrument detected" means passed as an argument to the
         # constructor of this class)
         return {
             'config': load(found_model, init_instr['id']),
             'id': init_instr['id']
         }
     elif found_model:
         # Instrument detected and no expected instrument specified
         return {
             'config': load(found_model, init_instr['id']),
             'id': init_instr['id']
         }
     elif expected_instr:
         # Expected instrument specified and no instrument detected
         return {
             'config': load(dummy_model_for_name(expected_instr)),
             'id': None
         }
     else:
         # No instrument detected or expected
         return {'config': None, 'id': None}
Esempio n. 24
0
def test_config_update(config_model):
    loaded = pipette_config.load(config_model)
    pip = pipette.Pipette(loaded, {
        'single': [0, 0, 0],
        'multi': [0, 0, 0]
    }, 'testID')
    sample_plunger_pos = {'top': 19.5}
    pip.update_config_item('top', sample_plunger_pos.get('top'))
    assert pip.config.top == sample_plunger_pos.get('top')
Esempio n. 25
0
def test_shake_during_drop(monkeypatch):
    robot.reset()
    pip = instruments._create_pipette_from_config(
            config=pipette_config.load('p1000_single_v2.0'),
            mount='left',
            name='p1000_single_v2.0')
    tiprack = containers_load(robot, 'opentrons_96_tiprack_1000ul', '1')

    shake_tips_drop = mock.Mock(
        side_effect=pip._shake_off_tips_drop)
    monkeypatch.setattr(pip, '_shake_off_tips_drop',
                        shake_tips_drop)

    # Test single shake for after pick up tips
    pip.tip_attached = True
    pip.drop_tip(tiprack.wells(0))
    assert shake_tips_drop.call_count == 1

    actual_calls = []

    def jog_side_effect(pose_tree, axis, distance):
        actual_calls.append((axis, distance))

    jog = mock.Mock(side_effect=jog_side_effect)
    monkeypatch.setattr(pip, '_jog', jog)

    # Test shake only in x, with no location passed, shake distance is 2.25
    shake_tips_drop()
    expected_calls = [('x', -2.25), ('x', 4.5), ('x', -2.25),
                      ('z', 20)]
    assert actual_calls == expected_calls

    # Test drop tip shake at a well with diameter above upper limit (2.25 mm)
    tiprack.wells(0).properties['width'] = 2.3*4
    actual_calls.clear()
    shake_tips_drop(tiprack.wells(0))
    expected_calls = [('x', -2.25), ('x', 4.5), ('x', -2.25),
                      ('z', 20)]
    assert actual_calls == expected_calls

    # Test drop tip shake at a well with diameter between upper limit
    # and lower limit (1.00 - 2.25 mm)
    tiprack.wells(0).properties['width'] = 2*4
    actual_calls.clear()
    shake_tips_drop(tiprack.wells(0))
    expected_calls = [('x', -2), ('x', 4), ('x', -2),
                      ('z', 20)]
    assert actual_calls == expected_calls

    # Test drop tip shake at a well with diameter below lower limit (1.00 mm)
    tiprack.wells(0).properties['width'] = 0.9*4
    actual_calls.clear()
    shake_tips_drop(tiprack.wells(0))
    expected_calls = [('x', -1), ('x', 2), ('x', -1),
                      ('z', 20)]
    assert actual_calls == expected_calls
    pip.tip_attached = False
Esempio n. 26
0
def test_versioned_aspiration(pipette_model, monkeypatch):

    monkeypatch.setattr(ff, 'use_old_aspiration_functions', lambda: True)
    was = pipette_config.load(pipette_model)
    check_sequences_close(
        was.ul_per_mm['aspirate'],
        defs['config'][pipette_model]['ulPerMm'][0]['aspirate'])
    check_sequences_close(
        was.ul_per_mm['dispense'],
        defs['config'][pipette_model]['ulPerMm'][0]['dispense'])

    monkeypatch.setattr(ff, 'use_old_aspiration_functions', lambda: False)
    now = pipette_config.load(pipette_model)
    check_sequences_close(
        now.ul_per_mm['aspirate'],
        defs['config'][pipette_model]['ulPerMm'][-1]['aspirate'])
    check_sequences_close(
        now.ul_per_mm['dispense'],
        defs['config'][pipette_model]['ulPerMm'][-1]['dispense'])
    assert now.ul_per_mm['aspirate'] != was.ul_per_mm['aspirate']
Esempio n. 27
0
def test_pipette_models_reach_max_volume(robot, instruments):

    for model in pipette_config.config_models:
        config = pipette_config.load(model)
        robot.reset()
        pipette = instruments._create_pipette_from_config(config=config,
                                                          mount='right',
                                                          name=model)

        pipette.tip_attached = True
        pipette.aspirate(pipette.max_volume)
        pos = pose_tracker.absolute(robot.poses, pipette.instrument_actuator)
        assert pos[0] < pipette.plunger_positions['top']
Esempio n. 28
0
def test_execute_extra_labware(protocol, protocol_file, monkeypatch,
                               virtual_smoothie_env, mock_get_attached_instr):
    fixturedir = HERE / '..' / '..' / '..' /\
        'shared-data' / 'labware' / 'fixtures' / '2'
    entries = []

    def emit_runlog(entry):
        nonlocal entries
        entries.append(entry)

    mock_get_attached_instr.return_value[types.Mount.RIGHT] = {
        'config': load('p300_single_v2.0'), 'id': 'testid'}
    # make sure we can load labware explicitly
    # make sure we don't have an exception from not finding the labware
    execute.execute(protocol.filelike, 'custom_labware.py',
                    emit_runlog=emit_runlog,
                    custom_labware_paths=[str(fixturedir)])
    # instead of 4 in simulate because we get before and after
    assert len(entries) == 8

    protocol.filelike.seek(0)
    # make sure we don't get autoload behavior when not on a robot
    with pytest.raises(ExceptionInProtocolError,
                       match='.*FileNotFoundError.*'):
        execute.execute(protocol.filelike, 'custom_labware.py')
    no_lw = execute.get_protocol_api('2.0')
    assert not no_lw._extra_labware
    protocol.filelike.seek(0)
    monkeypatch.setattr(execute, 'IS_ROBOT', True)
    monkeypatch.setattr(execute, 'JUPYTER_NOTEBOOK_LABWARE_DIR',
                        fixturedir)
    # make sure we don't have an exception from not finding the labware
    entries = []
    execute.execute(protocol.filelike, 'custom_labware.py',
                    emit_runlog=emit_runlog)
    # instead of 4 in simulate because we get before and after
    assert len(entries) == 8

    # make sure the extra labware loaded by default is right
    ctx = execute.get_protocol_api('2.0')
    assert len(ctx._extra_labware.keys()) == len(os.listdir(fixturedir))

    assert ctx.load_labware('fixture_12_trough', 1, namespace='fixture')

    # if there is no labware dir, make sure everything still works
    monkeypatch.setattr(execute, 'JUPYTER_NOTEBOOK_LABWARE_DIR',
                        HERE / 'nosuchdirectory')
    ctx = execute.get_protocol_api('2.0')
    with pytest.raises(FileNotFoundError):
        ctx.load_labware("fixture_12_trough", 1, namespace='fixture')
Esempio n. 29
0
def test_ul_per_mm_continuous(pipette_model):
    """
    For each model of pipette, for each boundary between pieces of the
    piecewise function describing the ul/mm relationship, test that the
    function is continuous.

    This test is utilizing the intermediate value theorem to determine
    whether a value c lives in the bounds of [a, b]. In this case, we are
    checking that given volumes (X) in a range of lower middle and max, the
    output (Y) of the func lives within the range of lower and max.

    See here for further details:
    https://en.wikipedia.org/wiki/Intermediate_value_theorem
    """
    config = pipette_config.load(pipette_model)
    aspirate = config.ul_per_mm['aspirate']
    dispense = config.ul_per_mm['dispense']
    min_vol = 0.000001  # sufficiently small starting volume
    for lno in range(len(aspirate) - 1):
        line = aspirate[lno]
        curr_max_vol = line[0]
        # find a halfway point roughly between max and min volume for a given
        # piecewise sequence of a pipette function
        half_max_vol = (curr_max_vol - min_vol) / 2 + min_vol

        min_ul_per_mm = line[1] * min_vol + line[2]
        mid_ul_per_mm = line[1] * half_max_vol + line[2]
        max_ul_per_mm = line[1] * curr_max_vol + line[2]

        lower_mm = min_ul_per_mm / min_vol
        higher_mm = max_ul_per_mm / curr_max_vol
        half_mm = mid_ul_per_mm / half_max_vol

        range_1 = (half_mm >= lower_mm) and (half_mm <= higher_mm)
        range_2 = (half_mm <= lower_mm) and (half_mm >= higher_mm)

        assert range_1 or range_2

        min_vol = curr_max_vol
    # make sure the mm of movement for max aspirate and max dispense agree
    aspirate_seq = aspirate[len(aspirate) - 1]
    dispense_seq = dispense[len(dispense) - 1]
    pip_max_vol = config.max_volume
    aspirate_mm = (aspirate_seq[1] * pip_max_vol +
                   aspirate_seq[2]) / pip_max_vol
    dispense_mm = (dispense_seq[1] * pip_max_vol +
                   dispense_seq[2]) / pip_max_vol
    # for many of the older pipettes, the aspirate and dispense values are
    # not the same.
    assert isclose(round(aspirate_mm), round(dispense_mm))
Esempio n. 30
0
def test_tip_tracking():
    pip = pipette.Pipette(pipette_config.load('p10_single_v1'), {
        'single': [0, 0, 0],
        'multi': [0, 0, 0]
    }, 'testID')
    with pytest.raises(AssertionError):
        pip.remove_tip()
    assert not pip.has_tip
    tip_length = 25.0
    pip.add_tip(tip_length)
    assert pip.has_tip
    with pytest.raises(AssertionError):
        pip.add_tip(tip_length)
    pip.remove_tip()
    assert not pip.has_tip
    with pytest.raises(AssertionError):
        pip.remove_tip()