Skip to content

Commit

Permalink
Merge branch 'edge' into EXEC-281-volume-tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
pmoegenburg committed Oct 24, 2024
2 parents 53e0e04 + 1d7b1f7 commit 64873fb
Show file tree
Hide file tree
Showing 206 changed files with 6,612 additions and 1,287 deletions.
2 changes: 1 addition & 1 deletion api-client/src/runs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export interface RunsLinks {
}

export interface RunCommandLink {
current: CommandLinkNoMeta
lastCompleted: CommandLinkNoMeta
}

export interface CommandLinkNoMeta {
Expand Down
29 changes: 14 additions & 15 deletions api/docs/v2/new_examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ Opentrons electronic pipettes can do some things that a human cannot do with a p
location=3)
p300 = protocol.load_instrument(
instrument_name="p300_single",
mount="right",
mount="left",
tip_racks=[tiprack_1])
p300.pick_up_tip()
Expand Down Expand Up @@ -442,13 +442,13 @@ This protocol dispenses diluent to all wells of a Corning 96-well plate. Next, i
source = reservoir.wells()[i]
row = plate.rows()[i]
# transfer 30 µL of source to first well in column
pipette.transfer(30, source, row[0], mix_after=(3, 25))
# transfer 30 µL of source to first well in column
pipette.transfer(30, source, row[0], mix_after=(3, 25))
# dilute the sample down the column
pipette.transfer(
30, row[:11], row[1:],
mix_after=(3, 25))
# dilute the sample down the column
pipette.transfer(
30, row[:11], row[1:],
mix_after=(3, 25))
.. tab:: OT-2

Expand All @@ -474,7 +474,7 @@ This protocol dispenses diluent to all wells of a Corning 96-well plate. Next, i
location=4)
p300 = protocol.load_instrument(
instrument_name="p300_single",
mount="right",
mount="left",
tip_racks=[tiprack_1, tiprack_2])
# Dispense diluent
p300.distribute(50, reservoir["A12"], plate.wells())
Expand All @@ -483,16 +483,15 @@ This protocol dispenses diluent to all wells of a Corning 96-well plate. Next, i
for i in range(8):
# save the source well and destination column to variables
source = reservoir.wells()[i]
source = reservoir.wells()[i]
row = plate.rows()[i]
# transfer 30 µL of source to first well in column
p300.transfer(30, source, row[0], mix_after=(3, 25))
# transfer 30 µL of source to first well in column
p300.transfer(30, source, row[0], mix_after=(3, 25))
# dilute the sample down the column
p300.transfer(
30, row[:11], row[1:],
mix_after=(3, 25))
# dilute the sample down the column
p300.transfer(
30, row[:11], row[1:],
mix_after=(3, 25))
Notice here how the code sample loops through the rows and uses slicing to distribute the diluent. For information about these features, see the Loops and Air Gaps examples above. See also, the :ref:`tutorial-commands` section of the Tutorial.

Expand Down
28 changes: 26 additions & 2 deletions api/src/opentrons/protocol_engine/state/frustum_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
SphericalSegment,
ConicalFrustum,
CuboidalFrustum,
SquaredConeSegment,
)


Expand Down Expand Up @@ -127,6 +128,15 @@ def _volume_from_height_spherical(
return volume


def _volume_from_height_squared_cone(
target_height: float, segment: SquaredConeSegment
) -> float:
"""Find the volume given a height within a squared cone segment."""
heights = segment.height_to_volume_table.keys()
best_fit_height = min(heights, key=lambda x: abs(x - target_height))
return segment.height_to_volume_table[best_fit_height]


def _height_from_volume_circular(
volume: float,
total_frustum_height: float,
Expand Down Expand Up @@ -197,15 +207,24 @@ def _height_from_volume_spherical(
return height


def _height_from_volume_squared_cone(
target_volume: float, segment: SquaredConeSegment
) -> float:
"""Find the height given a volume within a squared cone segment."""
volumes = segment.volume_to_height_table.keys()
best_fit_volume = min(volumes, key=lambda x: abs(x - target_volume))
return segment.volume_to_height_table[best_fit_volume]


def _get_segment_capacity(segment: WellSegment) -> float:
section_height = segment.topHeight - segment.bottomHeight
match segment:
case SphericalSegment():
return _volume_from_height_spherical(
target_height=segment.topHeight,
radius_of_curvature=segment.radiusOfCurvature,
)
case CuboidalFrustum():
section_height = segment.topHeight - segment.bottomHeight
return _volume_from_height_rectangular(
target_height=section_height,
bottom_length=segment.bottomYDimension,
Expand All @@ -215,13 +234,14 @@ def _get_segment_capacity(segment: WellSegment) -> float:
total_frustum_height=section_height,
)
case ConicalFrustum():
section_height = segment.topHeight - segment.bottomHeight
return _volume_from_height_circular(
target_height=section_height,
total_frustum_height=section_height,
bottom_radius=(segment.bottomDiameter / 2),
top_radius=(segment.topDiameter / 2),
)
case SquaredConeSegment():
return _volume_from_height_squared_cone(section_height, segment)
case _:
# TODO: implement volume calculations for truncated circular and rounded rectangular segments
raise NotImplementedError(
Expand Down Expand Up @@ -275,6 +295,8 @@ def height_at_volume_within_section(
top_width=section.topXDimension,
top_length=section.topYDimension,
)
case SquaredConeSegment():
return _height_from_volume_squared_cone(target_volume_relative, section)
case _:
raise NotImplementedError(
"Height from volume calculation not yet implemented for this well shape."
Expand Down Expand Up @@ -309,6 +331,8 @@ def volume_at_height_within_section(
top_width=section.topXDimension,
top_length=section.topYDimension,
)
case SquaredConeSegment():
return _volume_from_height_squared_cone(target_height_relative, section)
case _:
# TODO(cm): this would be the NEST-96 2uL wells referenced in EXEC-712
# we need to input the math attached to that issue
Expand Down
16 changes: 16 additions & 0 deletions api/src/opentrons/protocol_runner/run_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@ def get_current_command(self) -> Optional[CommandPointer]:
"""Get the "current" command, if any."""
return self._protocol_engine.state_view.commands.get_current()

def get_most_recently_finalized_command(self) -> Optional[CommandPointer]:
"""Get the most recently finalized command, if any."""
most_recently_finalized_command = (
self._protocol_engine.state_view.commands.get_most_recently_finalized_command()
)
return (
CommandPointer(
command_id=most_recently_finalized_command.command.id,
command_key=most_recently_finalized_command.command.key,
created_at=most_recently_finalized_command.command.createdAt,
index=most_recently_finalized_command.index,
)
if most_recently_finalized_command
else None
)

def get_command_slice(
self, cursor: Optional[int], length: int, include_fixit_commands: bool
) -> CommandSlice:
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/util/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]:
},
"sensor": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "basic",
"formatter": "message_only",
"filename": sensor_log_filename,
"maxBytes": 1000000,
"level": logging.DEBUG,
Expand Down
2 changes: 1 addition & 1 deletion app/src/assets/localization/en/pipette_wizard_flows.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"install_probe": "Take the calibration probe from its storage location. Ensure its collar is unlocked. Push the pipette ejector up and press the probe firmly onto the <bold>{{location}}</bold> pipette nozzle. Twist the collar to lock the probe. Test that the probe is secure by gently pulling it back and forth.",
"loose_detach": "Loosen screws and detach ",
"move_gantry_to_front": "Move gantry to front",
"must_detach_mounting_plate": "You must detach the mounting plate and reattach the z-axis carraige before using other pipettes. We do not recommend exiting this process before completion.",
"must_detach_mounting_plate": "You must detach the mounting plate and reattach the z-axis carriage before using other pipettes. We do not recommend exiting this process before completion.",
"name_and_volume_detected": "{{name}} Pipette Detected",
"next": "next",
"ninety_six_channel": "{{ninetySix}} pipette",
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux'

import {
RUN_STATUS_IDLE,
Expand All @@ -14,6 +15,7 @@ import {
useTrackEvent,
} from '/app/redux/analytics'
import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics'
import { getMissingSetupSteps } from '/app/redux/protocol-runs'
import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks'
import { isAnyHeaterShakerShaking } from '../../../RunHeaderModalContainer/modals'
import {
Expand All @@ -24,6 +26,8 @@ import {

import type { IconName } from '@opentrons/components'
import type { BaseActionButtonProps } from '..'
import type { State } from '/app/redux/types'
import type { StepKey } from '/app/redux/protocol-runs'

interface UseButtonPropertiesProps extends BaseActionButtonProps {
isProtocolNotReady: boolean
Expand All @@ -42,7 +46,6 @@ interface UseButtonPropertiesProps extends BaseActionButtonProps {
export function useActionButtonProperties({
isProtocolNotReady,
runStatus,
missingSetupSteps,
robotName,
runId,
confirmAttachment,
Expand All @@ -66,6 +69,9 @@ export function useActionButtonProperties({
const isHeaterShakerInProtocol = useIsHeaterShakerInProtocol()
const isHeaterShakerShaking = isAnyHeaterShakerShaking(attachedModules)
const trackEvent = useTrackEvent()
const missingSetupSteps = useSelector<State, StepKey[]>((state: State) =>
getMissingSetupSteps(state, runId)
)

let buttonText = ''
let handleButtonClick = (): void => {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { useSelector } from 'react-redux'
import { RUN_STATUS_IDLE, RUN_STATUS_STOPPED } from '@opentrons/api-client'
import { useConditionalConfirm } from '@opentrons/components'

import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks'
import { isAnyHeaterShakerShaking } from '../modals'
import { getMissingSetupSteps } from '/app/redux/protocol-runs'

import type { UseConditionalConfirmResult } from '@opentrons/components'
import type { RunStatus, AttachedModule } from '@opentrons/api-client'
import type { ConfirmMissingStepsModalProps } from '../modals'
import type { State } from '/app/redux/types'
import type { StepKey } from '/app/redux/protocol-runs'

interface UseMissingStepsModalProps {
runStatus: RunStatus | null
attachedModules: AttachedModule[]
missingSetupSteps: string[]
runId: string
handleProceedToRunClick: () => void
}

Expand All @@ -30,12 +34,14 @@ export type UseMissingStepsModalResult =
export function useMissingStepsModal({
attachedModules,
runStatus,
missingSetupSteps,
runId,
handleProceedToRunClick,
}: UseMissingStepsModalProps): UseMissingStepsModalResult {
const isHeaterShakerInProtocol = useIsHeaterShakerInProtocol()
const isHeaterShakerShaking = isAnyHeaterShakerShaking(attachedModules)

const missingSetupSteps = useSelector<State, StepKey[]>((state: State) =>
getMissingSetupSteps(state, runId)
)
const shouldShowHSConfirm =
isHeaterShakerInProtocol &&
!isHeaterShakerShaking &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,27 @@ import {
TYPOGRAPHY,
Modal,
} from '@opentrons/components'
import {
LPC_STEP_KEY,
LABWARE_SETUP_STEP_KEY,
LIQUID_SETUP_STEP_KEY,
MODULE_SETUP_STEP_KEY,
ROBOT_CALIBRATION_STEP_KEY,
} from '/app/redux/protocol-runs'
import type { StepKey } from '/app/redux/protocol-runs'

const STEP_KEY_TO_I18N_KEY = {
[LPC_STEP_KEY]: 'applied_labware_offsets',
[LABWARE_SETUP_STEP_KEY]: 'labware_placement',
[LIQUID_SETUP_STEP_KEY]: 'liquids',
[MODULE_SETUP_STEP_KEY]: 'module_setup',
[ROBOT_CALIBRATION_STEP_KEY]: 'robot_calibration',
}

export interface ConfirmMissingStepsModalProps {
onCloseClick: () => void
onConfirmClick: () => void
missingSteps: string[]
missingSteps: StepKey[]
}
export const ConfirmMissingStepsModal = (
props: ConfirmMissingStepsModalProps
Expand All @@ -41,7 +57,7 @@ export const ConfirmMissingStepsModal = (
missingSteps: new Intl.ListFormat('en', {
style: 'short',
type: 'conjunction',
}).format(missingSteps.map(step => t(step))),
}).format(missingSteps.map(step => t(STEP_KEY_TO_I18N_KEY[step]))),
})}
</LegacyStyledText>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export function useRunHeaderModalContainer({
runStatus,
runRecord,
attachedModules,
missingSetupSteps,
protocolRunControls,
runErrors,
}: UseRunHeaderModalContainerProps): UseRunHeaderModalContainerResult {
Expand Down Expand Up @@ -102,7 +101,7 @@ export function useRunHeaderModalContainer({
const missingStepsModalUtils = useMissingStepsModal({
attachedModules,
runStatus,
missingSetupSteps,
runId,
handleProceedToRunClick,
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ vi.mock('react-router-dom')
vi.mock('@opentrons/react-api-client')
vi.mock('/app/redux-resources/robots')
vi.mock('/app/resources/runs')
vi.mock('/app/redux/protocol-runs')
vi.mock('../RunHeaderModalContainer')
vi.mock('../RunHeaderBannerContainer')
vi.mock('../RunHeaderContent')
Expand All @@ -51,7 +52,6 @@ describe('ProtocolRunHeader', () => {
robotName: MOCK_ROBOT,
runId: MOCK_RUN_ID,
makeHandleJumpToStep: vi.fn(),
missingSetupSteps: [],
}

vi.mocked(useNavigate).mockReturnValue(mockNavigate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export interface ProtocolRunHeaderProps {
robotName: string
runId: string
makeHandleJumpToStep: (index: number) => () => void
missingSetupSteps: string[]
}

export function ProtocolRunHeader(
Expand Down
Loading

0 comments on commit 64873fb

Please sign in to comment.