Download policy.py from mitawa/m1001a: direct link, hf CLI and curl.
- Browser
- Download file 67.6 kB
-
https://huggingface.co/mitawa/m1001a/resolve/main/policy.py
- Command line
-
hf download hf://mitawa/m1001a/policy.py
-
curl -L -o policy.py https://huggingface.co/mitawa/m1001a/resolve/main/policy.py
67.6 kB
from copy import deepcopy
import dataclasses
import io
import os
import pathlib
import time
import numpy as np
from PIL import Image
from scipy.spatial.transform import Rotation
os.environ.setdefault("XLA_PYTHON_CLIENT_PREALLOCATE", "false")
JPEG_QUALITY = 90
SIDES = ("left", "right")
POSE_KEY = "observation.pose.left_hand_root_to_right_hand_root.absolute"
_K = ("observation.image.left", "observation.image.right", POSE_KEY,
"observation.joint_states", "prompt")
CUP_PA1 = "Pick up the cup with your right hand and set it on the plate"
CUP_PA2 = "Pick up the cup with your left hand and return the cup to its original position"
CUP_HANDS = {CUP_PA1: ("right", 15), CUP_PA2: ("left", 14)}
CUP_ADVANCE = {CUP_PA1: 8, CUP_PA2: 4}
CUP_NOISE = {CUP_PA1: .15, CUP_PA2: .10}
PEN_PROMPTS = (
"Pick up a pencil on the table and put it into the pen holder",
"Pick up a ballpoint pen on the table and put it into the pen holder",
)
def _asset_id(ckpt):
found = sorted(p.parent.name for p in (ckpt / "assets").glob("*/norm_stats.json"))
if len(found) != 1:
raise FileNotFoundError(f"expected one assets/<id>/norm_stats.json in {ckpt}, found {found}")
return found[0]
def _m(r):
a, b = r[..., :3], r[..., 3:6]
c0 = a / np.linalg.norm(a, axis=-1, keepdims=True)
b = b - np.sum(c0 * b, axis=-1, keepdims=True) * c0
c1 = b / np.linalg.norm(b, axis=-1, keepdims=True)
return np.stack([c0, c1, np.cross(c0, c1)], axis=-1)
def _d(x):
rot = Rotation.from_matrix(_m(x[:, 3:9]))
pr = Rotation.concatenate([Rotation.identity(), rot[:-1]])
pp = np.concatenate([np.zeros((1, 3)), x[:-1, :3]])
return np.concatenate([pr.inv().apply(x[:, :3] - pp), (pr.inv() * rot).as_quat(canonical=True)], axis=1)
def _image(img):
from openpi.shared import image_tools
a = np.asarray(image_tools.resize_with_pad(np.asarray(img, dtype=np.uint8), 224, 224))
b = io.BytesIO(); Image.fromarray(a).save(b, format="JPEG", quality=JPEG_QUALITY)
return np.asarray(Image.open(io.BytesIO(b.getvalue())).convert("RGB"))
def _build_transforms():
from openpi import transforms as T
from openpi.training.config import DataConfigFactory, ModelTransformFactory
@dataclasses.dataclass(frozen=True)
class _P(T.DataTransformFn):
def __call__(self, d):
return {k: d[k] for k in _K if k in d}
@dataclasses.dataclass(frozen=True)
class _I(T.DataTransformFn):
n: int = 32
def __call__(self, d):
l, r = _image(d[_K[0]]), _image(d[_K[1]])
p = np.array(d[_K[2]], dtype=np.float64)
if p[6] < 0:
p[3:] *= -1
s = np.concatenate([p, np.asarray(d[_K[3]], dtype=np.float64)]).astype(np.float32)
o = {"state": T.pad_to_dim(s, self.n),
"image": {"base_0_rgb": np.zeros_like(l), "left_wrist_0_rgb": l, "right_wrist_0_rgb": r},
"image_mask": {"base_0_rgb": np.False_, "left_wrist_0_rgb": np.True_, "right_wrist_0_rgb": np.True_}}
if "prompt" in d:
o["prompt"] = d["prompt"]
return o
@dataclasses.dataclass(frozen=True)
class _O(T.DataTransformFn):
def __call__(self, d):
a = np.asarray(d["actions"], dtype=np.float64)
return {"actions": np.concatenate([_d(a[:, 0:9]), _d(a[:, 9:18]), a[:, 18:20]], axis=1).astype(np.float32)}
@dataclasses.dataclass(frozen=True)
class _C(DataConfigFactory):
def create(self, assets_dirs, model_config):
return dataclasses.replace(
self.create_base_config(assets_dirs, model_config),
repack_transforms=T.Group(inputs=[_P()]),
data_transforms=T.Group(inputs=[_I(model_config.action_dim)], outputs=[_O()]),
model_transforms=ModelTransformFactory()(model_config))
return _C
class CupContinuousAdvance:
def __init__(self, advance_rows):
if type(advance_rows) is not int or not 1 <= advance_rows <= 16:
raise ValueError('Continuous place advance must be in 1..16 rows')
self.advance_rows = advance_rows
self.state = dict(prompt=None, clock=0, saw_open=False, armed=False, deadline=None,
waveform=None, latched=False, cursor=0, done=False)
self._generation = 0
self._owner = object()
def propose(self, actions, obs, adopt_rows):
a = np.asarray(actions)
if a.shape != (32, 16) or a.dtype != np.float32 or not np.isfinite(a).all():
raise ValueError('Continuous place advance expects finite float32[32,16]')
if type(adopt_rows) is not int or not 1 <= adopt_rows <= 32:
raise ValueError('Continuous place advance requires 1..32 adopted rows')
prompt = obs.get('prompt', '').strip().rstrip('.')
if prompt not in CUP_HANDS:
raise ValueError('Continuous place advance requires task1 PA1/PA2')
fingers = np.asarray(obs['observation.joint_states'], dtype=float)
if fingers.shape != (2,) or not np.isfinite(fingers).all():
raise ValueError('Expected two finite measured finger angles')
side, column = CUP_HANDS[prompt]
hand = column - 14
state = deepcopy(self.state)
if state['prompt'] != prompt:
state = dict(prompt=prompt, clock=0, saw_open=False, armed=False, deadline=None,
waveform=None, latched=False, cursor=0, done=False)
output = a.copy()
if not state['done'] and not state['armed']:
if fingers[hand] > .53:
state['saw_open'] = True
elif state['saw_open'] and 3 * 2 * np.pi / 4096 <= fingers[hand] <= .53:
state['armed'] = True
raw = a[:, column]
threshold = min(.53, float(raw[0]) + .05)
opening = None
if np.any(raw >= .56):
for i in range(31):
if raw[i] >= threshold and raw[i + 1] >= threshold:
opening = i + 1
break
if state['armed'] and fingers[hand] >= .56 and raw[0] >= .56:
state.update(latched=False, done=True, deadline=None, waveform=None)
start = None
if state['armed'] and not state['latched'] and not state['done']:
if opening is None:
state.update(deadline=None, waveform=None)
else:
if state['deadline'] is None:
state['deadline'] = state['clock'] + opening
state['waveform'] = raw[opening - 1:].tolist()
desired = state['deadline'] - state['clock'] - self.advance_rows
candidate = max(desired, 1)
if candidate <= adopt_rows and candidate < opening:
start = candidate
natural_start = (opening if state['armed'] and not state['done']
and not state['latched'] and start is None
and opening is not None and opening <= adopt_rows else None)
report = dict(enabled=True, kind='cup_continuous_place_advance_v1',
hand=side, advance_rows=self.advance_rows, clock=state['clock'],
forecast_opening_row=opening, opening_threshold_rad=threshold,
anchored_opening_row=state['deadline'], forced_start_row=start,
natural_start_row=natural_start,
already_latched=state['latched'], modified=False,
waveform='continuous values from the first reserved forecast',
pick_advance=False, height_or_robot_state_used=False)
release_row = start or natural_start
hold_end = ((release_row - 1) // 4 + 1) * 4 if release_row else 0
states = []
for index in range(adopt_rows):
if start == index + 1:
state.update(latched=True, cursor=0, open_floor=float(fingers[hand]))
if state['latched']:
wave = state['waveform']
if not wave:
raise RuntimeError('Missing reserved continuous opening waveform')
value = max(float(fingers[hand]), state.get('open_floor', 0.),
wave[min(state['cursor'], len(wave)-1)])
output[index, column] = value
state.update(cursor=state['cursor']+1, open_floor=value)
report['modified'] = True
if release_row and release_row <= index + 1 <= hold_end:
output[index, :14] = np.tile([0., 0., 0., 0., 0., 0., 1.], 2)
report['modified'] = True
state['clock'] += 1
states.append(deepcopy(state))
return dict(actions=output, states=states, report=report,
adopt_rows=adopt_rows, _owner=self._owner, _generation=self._generation)
def commit(self, proposal, executed_rows):
if proposal.get('_owner') is not self._owner or proposal.get('_generation') != self._generation:
raise ValueError('Stale continuous place advance proposal')
if type(executed_rows) is not int or not 0 <= executed_rows <= proposal['adopt_rows']:
raise ValueError('Invalid verified executed prefix')
if executed_rows:
self.state = deepcopy(proposal['states'][executed_rows - 1])
self._generation += 1
PEN_POLICY = dict(kind='u1_pen_delayed_bidirectional_32_retention_narrow_retry_v2',
lookahead_rows=16, retained_forecast_rows=32, command_row_1based=8,
delay_completed_chunks=1, close_below_rad=.07, release_at_or_above_rad=.27,
preclose_open_range_rad=[.07, .27], close_cancel_opening_delta_rad=.02,
close_target_rad=0., open_target='first_remaining_raw_release_value',
latch_max_completed_chunks=2, latch_release='natural_prefix_catchup_or_countermand_or_forecast_withdrawal_or_bound',
initial_closed_is_verified_grasp=False, narrow_retry='current_not_holding_plus_future_close_and_no_pending_open_or_open_latch_or_prefix_open', state_commit='verified_full_eight_rows_only',
raw_four_stage_widths_preserved=True, ground_truth_inputs=False)
def policy_for_rows(rows=8, *, place_advance_rows=0, close_lookahead=True, close_defer_only=False):
if type(rows) is not int or rows not in (8, 16):
raise ValueError('U1 pen lookahead execution rows must be8 or16')
if type(close_defer_only) is not bool or (close_defer_only and (not close_lookahead or rows != 8)):
raise ValueError('Confirmed defer-only requires close lookahead enabled and adopt8')
if type(close_lookahead) is not bool:
raise ValueError('close_lookahead must be boolean')
if type(place_advance_rows) is not int or not 0 <= place_advance_rows <= 16:
raise ValueError('Pen place advance must be an integer in0..16')
policy = (deepcopy(PEN_POLICY) if rows == 8 else
dict(deepcopy(PEN_POLICY), kind='u1_pen_delayed_bidirectional_32_retention_adopt16_v3',
lookahead_rows=32, command_row_1based=16, state_commit='verified_full_sixteen_rows_only'))
if place_advance_rows:
policy.update(kind=policy['kind']+'_place_advance_v1', place_advance_rows=place_advance_rows,
release_rule='first raw >=0.27 forecast absolute executed-row deadline minus advance; earliest current row1',
release_target='first current raw opening value; existing bounded open latch',
release_forecast_rows=32, release_withdrawal_cancels=True,
pick_advance_changed=False, arm_actions_changed=False)
if not close_lookahead:
policy.update(kind=policy['kind']+'_open_only_v1', close_lookahead_enabled=False,
close_rule='raw model timing only; no forced-close reservation or latch',
normal_model_closing_preserved=True)
if close_defer_only:
policy.update(kind=policy['kind']+'_confirmed_close_defer_v1', close_defer_only=True,
close_defer_near_rows=12, close_defer_confirmations=2, close_defer_reserve_rows=16,
close_defer_deadline='first eligible raw close forecast; absolute verified executed-row clock',
close_defer_trigger='deadline already expired, raw close within first12 and delayed for two consecutive completed observations',
close_defer_command_row=rows, normal_model_closing_preserved=True)
return policy
class PenGraspSchedule:
def __init__(self, rows=8, *, place_advance_rows=0, close_lookahead=True, close_defer_only=False):
self.rows = rows
self.policy = policy_for_rows(rows, place_advance_rows=place_advance_rows, close_lookahead=close_lookahead, close_defer_only=close_defer_only)
self.close_lookahead = close_lookahead
self.close_defer_only = close_defer_only
self.close_defer_state = {s: None for s in SIDES}
self.place_advance_rows = place_advance_rows
self.release_deadlines = {s: None for s in SIDES}
self.executed_row_clock = 0
self.pending = {s: None for s in SIDES}
self.phases = {s: None for s in SIDES}
self.verified_close = {s: False for s in SIDES}
self.seen_open = {s: False for s in SIDES}
self.latches = {s: None for s in SIDES}
self.last_committed_cycle = -1
self._generation = 0
self._owner = id(self)
def propose(self, cycle, predictions, targets, measured_angles, readings, limits, *, allow_clipping):
prediction = np.asarray(predictions)
selected = np.asarray(targets, dtype=float).copy()
measured = np.asarray(measured_angles, dtype=float)
if (type(cycle) is not int or cycle <= self.last_committed_cycle
or prediction.shape != (32, 16) or prediction.dtype.kind != 'f'
or not np.isfinite(prediction).all() or selected.shape != (self.rows, 2)
or not np.isfinite(selected).all() or measured.shape != (2,)
or not np.isfinite(measured).all()):
raise ValueError('Invalid U1 pen transition inputs/cycle')
next_close_defer = deepcopy(self.close_defer_state)
next_release_deadlines = deepcopy(self.release_deadlines)
next_pending = {s: None for s in SIDES}
next_phases = deepcopy(self.phases)
next_verified = deepcopy(self.verified_close)
next_seen = deepcopy(self.seen_open)
next_latches = {s: None for s in SIDES}
report = dict(enabled=True, policy=deepcopy(self.policy), cycle=cycle,
state_committed=False, pending_before=deepcopy(self.pending),
place_advance_rows=self.place_advance_rows, executed_row_clock=self.executed_row_clock,
close_defer_only=self.close_defer_only, close_defer_before=deepcopy(self.close_defer_state),
release_deadlines_before=deepcopy(self.release_deadlines),
phases_before=deepcopy(self.phases), latches_before=deepcopy(self.latches),
verified_close_before=deepcopy(self.verified_close), hands={},
_owner=self._owner, _generation=self._generation)
for col, side in enumerate(SIDES):
values = prediction[:, 14 + col]
adopted = selected[:, col].copy()
angle = float(measured[col])
holding = readings.get(side, {}).get('contact_force_hold') is True
verified = bool(self.verified_close[side] or holding)
seen_open = bool(self.seen_open[side] or angle >= .07)
if angle >= .27 and not holding:
verified = False
state = ('closed_holding' if holding else 'wide_open' if angle >= .27
else 'preclose_open' if angle >= .07 and not verified
else 'release_started' if angle >= .07
else 'closed' if verified else 'initial_closed_unverified')
phase = ('closed' if holding or (angle < .07 and verified)
else 'release_started' if verified else 'wide_open' if angle >= .27
else 'preclose_open' if angle >= .07 else 'initial_closed_unverified')
closes = np.flatnonzero(values < .07)
opens = np.flatnonzero(values >= .27)
close16 = closes[closes < self.policy['lookahead_rows']]
open16 = opens[opens < self.policy['lookahead_rows']]
prefix_close = np.flatnonzero(adopted < .07)
prefix_open = np.flatnonzero(adopted >= .27)
open_then_close = bool(len(prefix_open) and np.any(adopted[prefix_open[0]+1:] < .07))
pending_before = self.pending[side]
latch_before = self.latches[side]
retry_preclose = bool(verified and not holding and .07 <= angle < .27
and len(closes) and not len(prefix_close) and not len(prefix_open)
and not (pending_before and pending_before['direction'] == 'open')
and not (latch_before and latch_before['direction'] == 'open')
and (not len(opens) or closes[0] < opens[0]))
if retry_preclose:
phase = 'preclose_retry'
hand = dict(measured_angle_rad=angle, contact_force_hold=holding,
source_state=state, source_phase=phase, verified_close_history=verified, retry_after_verified_close=retry_preclose,
close_prediction_rows_1based=(closes + 1).tolist(),
open_prediction_rows_1based=(opens + 1).tolist(),
original_selected_row_rad=float(adopted[self.rows-1]), selected_row_rad=float(adopted[self.rows-1]),
model_row_rad=float(values[self.rows-1]), command_row_1based=self.rows, event='none', override=False,
scheduled_in_adopted_plan=False, calibrated_clipping=False,
override_rows_1based=[], latch_event='none', close_lookahead_enabled=self.close_lookahead)
hand[f'original_selected_row{self.rows}_rad'] = float(adopted[self.rows-1])
hand[f'selected_row{self.rows}_rad'] = float(adopted[self.rows-1])
hand[f'model_row{self.rows}_rad'] = float(values[self.rows-1])
def apply(rows, requested):
lo, hi = (float(limits[side][k]) for k in ('min_angle_rad', 'max_angle_rad'))
if not np.isfinite([lo, hi]).all() or lo > hi:
raise ValueError('Invalid measured gripper limits')
if not lo <= requested <= hi and not allow_clipping:
raise ValueError(side + ': pen transition target outside calibrated limits')
target = float(np.clip(requested, lo, hi))
selected[rows, col] = target
hand.update(override=True, requested_target_rad=float(requested),
calibrated_clipping=bool(hand['calibrated_clipping'] or target != requested))
hand['override_rows_1based'].extend((np.arange(self.rows)[rows] + 1).tolist())
return target
pending = self.pending[side]
latch = self.latches[side]
if not self.close_lookahead:
if pending is not None and pending['direction'] == 'close':
pending = None
hand['discarded_close_reservation'] = True
if latch is not None and latch['direction'] == 'close':
latch = None
hand['discarded_close_latch'] = True
if latch is not None:
direction = latch['direction']
remaining = closes if direction == 'close' else opens
catchup = prefix_close if direction == 'close' else prefix_open
countermand = bool(len(prefix_open)) if direction == 'close' else open_then_close
if countermand:
hand['latch_event'] = 'released_explicit_countermand'
elif not len(remaining):
hand['latch_event'] = 'released_forecast_withdrawn'
elif latch['completed_chunks'] >= self.policy['latch_max_completed_chunks']:
hand['latch_event'] = 'released_bounded_duration'
else:
stop = int(catchup[0]) if len(catchup) else self.rows
if stop:
apply(slice(0, stop), latch['target_rad'])
hand['latch_event'] = 'natural_prefix_catchup' if len(catchup) else 'maintain_short_latch'
if not len(catchup):
next_latches[side] = dict(latch, completed_chunks=latch['completed_chunks'] + 1)
hand['event'] = 'existing_latch'
elif pending is not None:
direction = pending['direction']
hand.update(direction=direction, detected_cycle=pending['detected_cycle'], due_cycle=pending['due_cycle'])
if cycle != pending['due_cycle']:
hand['event'] = 'cancelled_missed_due_cycle'
elif direction == 'close' and (holding or (angle < .07 and verified)):
hand['event'] = 'already_closed_measured'
elif direction == 'open' and state == 'wide_open':
hand['event'] = 'already_open_measured'
elif direction == 'close' and phase not in ('preclose_open', 'preclose_retry'):
hand['event'] = 'cancelled_source_state_changed'
elif direction == 'open' and not verified:
hand['event'] = 'cancelled_source_state_changed'
elif not len(closes if direction == 'close' else opens):
hand['event'] = 'cancelled_prediction_retracted_32'
elif direction == 'close' and np.any(adopted > pending['anchor_angle_rad'] + .02):
hand['event'] = 'cancelled_opening_command_before_deadline'
elif direction == 'open' and (open_then_close or (phase == 'release_started' and len(prefix_close))):
hand['event'] = 'cancelled_closing_command_before_deadline'
elif direction == 'close' and len(prefix_close):
hand['event'] = 'normal_policy_close_in_chunk'
elif direction == 'open' and len(prefix_open):
hand['event'] = 'normal_policy_open_in_chunk'
else:
requested = 0. if direction == 'close' else float(values[opens[0]])
actual = apply(slice(self.rows-1, self.rows), requested)
hand['event'] = 'apply_due_transition'
next_latches[side] = dict(direction=direction, target_rad=actual, completed_chunks=0)
else:
direction = None
if self.close_lookahead and not self.close_defer_only and phase in ('preclose_open', 'preclose_retry') and len(close16):
if np.any(adopted > angle + .02):
hand['event'] = 'not_armed_opening_command'
elif len(prefix_close):
hand['event'] = 'normal_policy_close_in_chunk'
else:
direction = 'close'
elif not self.place_advance_rows and verified and (holding or angle < .27) and len(open16):
if open_then_close:
hand['event'] = 'not_armed_open_then_close'
elif len(prefix_open):
hand['event'] = 'normal_policy_open_in_chunk'
else:
direction = 'open'
if direction:
next_pending[side] = dict(direction=direction, detected_cycle=cycle,
due_cycle=cycle+1, anchor_angle_rad=angle)
hand.update(event='arm_next_chunk', direction=direction,
detected_cycle=cycle, due_cycle=cycle+1)
if self.close_defer_only:
first_close = int(closes[0])+1 if len(closes) else None
check = dict(enabled=True, first_close_row=first_close, near_rows=32,
confirmations_required=1, previous=deepcopy(self.close_defer_state[side]),
modified=False, event='ineligible')
eligible = bool(phase in ('preclose_open','preclose_retry') and not holding
and len(closes) and not len(prefix_close) and not len(prefix_open)
and not (pending_before and pending_before['direction']=='open')
and not (latch_before and latch_before['direction']=='open')
and not np.any(adopted > angle+.02)
and (not len(opens) or closes[0] < opens[0]))
if not eligible or latch is not None:
next_close_defer[side] = None
check['event'] = 'natural_close_in_prefix' if len(prefix_close) else 'reset_ineligible_or_latch'
else:
old = self.close_defer_state[side]
deferred = deepcopy(old) if old is not None else None
if deferred is None and first_close <= 32:
deferred = dict(deadline=self.executed_row_clock+first_close, confirmations=0)
check['event'] = 'reserve_first_close_deadline'
if deferred is not None:
expected = deferred['deadline']-self.executed_row_clock
expired = self.executed_row_clock >= deferred['deadline']
near = first_close <= 32
delayed = first_close >= expected+2
confirm = bool(expired and near and delayed)
deferred['confirmations'] = deferred['confirmations']+1 if confirm else 0
check.update(deadline=deferred['deadline'], expected_close_row=expected,
deadline_expired=expired, near_close=near, delayed=delayed,
confirmations=deferred['confirmations'], event='observe_close_deferral')
if deferred['confirmations'] >= 1:
actual = apply(slice(self.rows-1,self.rows),0.)
hand.update(event='apply_confirmed_deferred_close',direction='close')
check.update(event='apply_confirmed_deferred_close',modified=True,
command_row_1based=self.rows,requested_target_rad=0.,selected_target_rad=actual)
next_pending[side] = None
next_latches[side] = dict(direction='close',target_rad=actual,completed_chunks=0)
deferred = None
next_close_defer[side] = deferred
hand['close_defer'] = check
if self.place_advance_rows:
advance = dict(enabled=True, advance_rows=self.place_advance_rows,
forecast_open_row=None if not len(opens) else int(opens[0])+1,
original_deadline=self.release_deadlines[side], target_row=None,
modified=False, event='not_in_release_state')
if not len(opens) or open_then_close:
next_release_deadlines[side] = None
advance['event'] = 'forecast_withdrawn' if not len(opens) else 'prefix_open_then_close'
elif latch is not None and latch['direction'] == 'open':
next_release_deadlines[side] = None
advance['event'] = 'existing_open_latch'
elif verified and (holding or angle < .27):
first_open = int(opens[0])+1
deadline = self.release_deadlines[side]
if deadline is None:
deadline = self.executed_row_clock + first_open
next_release_deadlines[side] = deadline
expected = min(deadline-self.executed_row_clock, first_open)
target_row = max(1, expected-self.place_advance_rows)
advance.update(original_deadline=deadline, expected_open_row=expected,
target_row=target_row, event='reserved_open_deadline')
if target_row <= self.rows and target_row < first_open:
requested = float(values[opens[0]])
actual = apply(slice(target_row-1,self.rows), requested)
advance.update(modified=True,event='apply_advanced_open',requested_target_rad=requested,
selected_target_rad=actual, previous_transition_event=hand['event'])
hand.update(event='apply_advanced_open',direction='open')
next_pending[side] = None
next_latches[side] = dict(direction='open',target_rad=actual,completed_chunks=0)
next_release_deadlines[side] = None
elif len(prefix_open):
advance['event'] = 'natural_open_in_prefix'
next_release_deadlines[side] = None
else:
next_release_deadlines[side] = None
hand['place_advance'] = advance
final = float(selected[self.rows-1, col])
next_verified[side] = bool(verified or (final < .07 and seen_open))
if final >= .27:
next_verified[side] = False
next_seen[side] = seen_open
next_phases[side] = ('closed' if next_verified[side] and final < .07
else 'release_started' if next_verified[side]
else 'wide_open' if final >= .27 else 'preclose_open' if final >= .07
else 'initial_closed_unverified')
hand.update(selected_row_rad=final, phase_after_selected_chunk=next_phases[side],
verified_close_after_selected_chunk=next_verified[side],
override_rows_1based=sorted(set(hand['override_rows_1based'])))
hand[f'selected_row{self.rows}_rad'] = final
report['hands'][side] = hand
report.update(proposed_close_defer=next_close_defer,
proposed_release_deadlines=next_release_deadlines,
proposed_pending=next_pending, proposed_phases=next_phases,
proposed_verified_close=next_verified, proposed_seen_open=next_seen,
proposed_latches=next_latches)
return selected, report
def annotate_plan(self, report, adopted_rows):
report['adopted_rows'] = adopted_rows
for hand in report['hands'].values():
hand['scheduled_in_adopted_plan'] = bool(hand['override'] and adopted_rows == self.rows)
def commit(self, report, *, executed_rows):
if (report.get('_owner') != self._owner or report.get('_generation') != self._generation
or report.get('state_committed') or report['cycle'] <= self.last_committed_cycle):
raise ValueError('Pen transition proposal already committed/stale/foreign')
if type(executed_rows) is not int or not 0 <= executed_rows <= self.rows:
raise ValueError('Invalid verified executed rows')
full = executed_rows == self.rows
if full:
self.close_defer_state = deepcopy(report['proposed_close_defer'])
self.release_deadlines = deepcopy(report['proposed_release_deadlines'])
self.pending = deepcopy(report['proposed_pending'])
self.phases = deepcopy(report['proposed_phases'])
self.verified_close = deepcopy(report['proposed_verified_close'])
self.seen_open = deepcopy(report['proposed_seen_open'])
self.latches = deepcopy(report['proposed_latches'])
else:
self.close_defer_state = {s: None for s in SIDES}
self.release_deadlines = {s: None for s in SIDES}
self.pending = {s: None for s in SIDES}
self.latches = {s: None for s in SIDES}
self.executed_row_clock += executed_rows
self.last_committed_cycle = report['cycle']
self._generation += 1
report.update(state_committed=True, executed_rows=executed_rows,
commit_result='full_chunk' if full else 'shortened_drop_pending',
close_defer_after=deepcopy(self.close_defer_state),
release_deadlines_after=deepcopy(self.release_deadlines),
executed_row_clock_after=self.executed_row_clock,
pending_after=deepcopy(self.pending), phases_after=deepcopy(self.phases),
latches_after=deepcopy(self.latches), verified_close_after=deepcopy(self.verified_close))
ARM_POLICY = dict(
kind="u1_pen_grasp_axis_pose_retention_v9",
correction_scale=1.25, approach_axis_hand=[1., 0., 0.],
grasp_width_min_rad=3 * 2 * np.pi / 4096,
closed_command_threshold_rad=.07, raw_forecast_horizon_rows=32,
progress_epsilon_m=1e-6, reference_max_translation_m=.005,
reference_max_rotation_rad=.05,
coordinate_reference="opposite hand, assumed stationary; contract inter-hand pose only",
direction="negative active-hand X at width-gated grasp, frozen in opposite-hand frame",
translation="configured scale times the old/latest target deficit along estimated up only",
history_source="raw predictions, never corrected predictions",
orientation="retain missing rotation progress around a frozen forecast axis; same correction scale as height",
rotation_axis_min_angle_rad=np.pi/180, rotation_progress_epsilon_rad=1e-5,
rotation_blend_in="smoothstep over executed prefix; full deficit at prefix endpoint",
grippers="unchanged; any independent client lookahead stays separate",
fixed_postgrasp_row_cutoff=False, world_inputs=False, robot_fk_inputs=False,
calibrated_transform_inputs=False, current_or_contact_inputs=False,
lift_success_classifier=False,
)
def regime(x):
return "closed" if x < .07 else "open" if x >= .27 else "narrow"
def pair(x):
a = np.asarray([x[s] for s in SIDES] if isinstance(x, dict) else x, dtype=float)
if a.shape != (2,) or not np.isfinite(a).all():
raise ValueError("Two finite finger values required")
return a
def matrix(pose):
p = np.asarray(pose, dtype=float)
if p.shape != (7,) or not np.isfinite(p).all():
raise ValueError("Finite xyz/xyzw pose required")
q = p[3:]; norm = np.linalg.norm(q)
if abs(norm - 1.) > .02:
raise ValueError("Unit xyzw quaternion required")
x, y, z, w = q / norm
t = np.eye(4)
t[:3, :3] = [[1-2*(y*y+z*z), 2*(x*y-z*w), 2*(x*z+y*w)],
[2*(x*y+z*w), 1-2*(x*x+z*z), 2*(y*z-x*w)],
[2*(x*z-y*w), 2*(y*z+x*w), 1-2*(x*x+y*y)]]
t[:3, 3] = p[:3]
return t
def inverse(t):
result = np.eye(4)
result[:3, :3] = t[:3, :3].T
result[:3, 3] = -result[:3, :3] @ t[:3, 3]
return result
def angle(r):
return float(np.arccos(np.clip((np.trace(r)-1.)/2., -1., 1.)))
def quaternion(r):
trace=float(np.trace(r))
if trace > 0:
s=2*np.sqrt(max(0.,trace+1.))
q=np.array([(r[2,1]-r[1,2])/s,(r[0,2]-r[2,0])/s,(r[1,0]-r[0,1])/s,s/4])
else:
i=int(np.argmax(np.diag(r)));j=(i+1)%3;k=(i+2)%3
s=2*np.sqrt(max(0.,1.+r[i,i]-r[j,j]-r[k,k]))
q=np.zeros(4);q[i]=s/4;q[j]=(r[i,j]+r[j,i])/s;q[k]=(r[i,k]+r[k,i])/s
q[3]=(r[k,j]-r[j,k])/s
q/=np.linalg.norm(q)
return -q if q[3]<0 else q
def axis_rotation(axis, radians):
x,y,z=axis
skew=np.array([[0.,-z,y],[z,0.,-x],[-y,x,0.]])
return np.eye(3)+np.sin(radians)*skew+(1-np.cos(radians))*(skew@skew)
def twist_angle(r, axis, reference=0.):
q=quaternion(r);v=float(q[:3]@axis);w=float(q[3])
if np.hypot(v,w)<1e-8:
raise ValueError("Rotation twist is singular")
value=2*np.arctan2(v,w)
return float(reference+(value-reference+np.pi)%(2*np.pi)-np.pi)
def twist_path(rotations, base, axis, reference):
result=[]
for r in rotations:
reference=twist_angle(r@base.T,axis,reference);result.append(reference)
return np.asarray(result)
def forecast(raw, starts):
poses = np.empty((len(raw), 2, 4, 4))
for h in range(2):
p = starts[h].copy()
for i, row in enumerate(raw):
p = p @ matrix(row[h*7:h*7+7])
poses[i, h] = p
return poses
class GraspAxisRetention:
def __init__(self, execution_rows=None, *, correction_scale=None):
if execution_rows is not None and (type(execution_rows) is not int
or execution_rows not in (8, 16)):
raise ValueError("Execution rows must be 8, 16, or None")
scale = ARM_POLICY["correction_scale"] if correction_scale is None else correction_scale
if isinstance(scale, (bool, np.bool_)) or not isinstance(scale, (int, float, np.integer, np.floating)) or not np.isfinite(scale) or not 0 <= scale <= 2:
raise ValueError("correction_scale must be a finite number in [0,2]")
self.scale = float(scale)
self.n = execution_rows
self.policy = dict(ARM_POLICY, execution_rows=execution_rows, correction_scale=self.scale,
max_old_age_chunks=None if execution_rows is None else 32//execution_rows-1)
self.reset()
def reset(self):
self.history = []
self.clock = self.generation = 0
self.held_last = [False, False]
self.held_rows = [0, 0]
self.grasp_directions = [None, None]
self.retention_done = [False, False]
self.rotation_bases = [None, None]
self.rotation_axes = [None, None]
self.rotation_retention_done = [False, False]
self.rotation_progress = [0., 0.]
self.observed_execution_rows = None
self.pending = None
self.last_prompt = None
self.last_report = None
def propose(self, raw, obs, targets=None, previous_applied=None):
raw = np.asarray(raw)
if raw.shape != (32, 16) or raw.dtype.kind != "f" or not np.isfinite(raw).all():
raise ValueError("Finite floating (32,16) raw predictions required")
relative = matrix(obs[POSE_KEY])
current = [inverse(relative), relative]
measured = pair(obs["observation.joint_states"])
previous = measured if previous_applied is None else pair(previous_applied)
n = self.n or 32
selected = np.asarray(raw[:n, 14:16] if targets is None else targets, dtype=float)
if selected.shape != (n, 2) or not np.isfinite(selected).all():
raise ValueError("Selected gripper targets must match the returned prefix")
poses = forecast(raw, current)
relative_motion = forecast(raw, [np.eye(4), np.eye(4)])
ref_rows = self.n or 16
stable = []
for h in range(2):
other = 1-h
translation = np.max(np.linalg.norm(relative_motion[:ref_rows, other, :3, 3], axis=1))
rotation = max(angle(t[:3, :3]) for t in relative_motion[:ref_rows, other])
stable.append(bool(translation <= ARM_POLICY["reference_max_translation_m"]
and rotation <= ARM_POLICY["reference_max_rotation_rad"]))
held, directions, done, resets, counts, reasons = [], [], [], [], [], []
for h in range(2):
gate = selected[:, h] if self.n else selected[:1, h]
sequence = [regime(previous[h])] + [regime(v) for v in gate]
transition = any(a != b for a, b in zip(sequence, sequence[1:]))
closing = all(x == "closed" for x in sequence)
width = ARM_POLICY["grasp_width_min_rad"]-1e-12 <= measured[h] < .07
active = bool(closing and width and stable[h])
held.append(active); resets.append(transition)
counts.append(self.held_rows[h] if active and self.held_last[h] else 0)
direction = self.grasp_directions[h] if active and self.held_last[h] else None
if active and direction is None:
direction = -current[h][:3, :3][:, 0].copy()
directions.append(direction)
done.append(self.retention_done[h] if active and self.held_last[h] else False)
why = []
if transition: why.append("selected_grip_transition")
if not closing or not width: why.append("width_gate_inactive")
if not stable[h]: why.append("opposite_hand_prediction_moves")
if done[h]: why.append("postgrasp_lift_phase_complete")
reasons.append(why)
output = raw.copy(); reports = {}; rotation_states = []
for h, side in enumerate(SIDES):
direction = directions[h]; start = current[h][:3, 3]
fresh = poses[:n, h, :3, 3]
additions = np.zeros(n); best = 0.; chosen = None
pending_forward = False; old_peak = 0.; fresh_peak = 0.
if held[h] and not done[h]:
progress = (fresh-start) @ direction
closed_end = next((i for i in range(32) if regime(raw[i, 14+h]) != "closed"), 32)
if closed_end:
fresh_peak = max(0., float(np.max((poses[:closed_end, h, :3, 3]-start) @ direction)))
for entry in self.history:
offset = self.clock-entry["birth"]
if resets[h] or not entry["valid"][h] or not 1 <= offset < 32:
continue
length = min(n, 32-offset)
if self.n and length != n: continue
mismatch = next((i for i in range(length)
if regime(entry["grips"][offset+i, h]) != regime(selected[i, h])
or regime(selected[i, h]) != "closed"), length)
if self.n and mismatch != length:
reasons[h].append("old_grip_regime_mismatch"); continue
length = mismatch
if not length: continue
old_progress = (entry["poses"][offset:offset+length, h, :3, 3]-start) @ direction
peak = float(np.max(old_progress)); old_peak = max(old_peak, peak)
if peak <= ARM_POLICY["progress_epsilon_m"]: continue
pending_forward = True
delta = np.zeros(n)
delta[:length] = np.maximum(np.maximum(old_progress, 0.)-progress[:length], 0.) * self.scale
amount = float(np.max(delta))
if amount > best:
best, additions, chosen = amount, delta, (entry, offset)
end = min(n, closed_end)
if (not pending_forward and fresh_peak <= ARM_POLICY["progress_epsilon_m"]
and end and progress[end-1] < -ARM_POLICY["progress_epsilon_m"]):
done[h] = True
reasons[h].append("no_unexecuted_upward_forecast; return_to_latest_model")
modified = False
rotation=self._rotation_proposal(h,current,poses,selected,held[h],resets[h],raw[:,14+h])
rotation_states.append(rotation["state"])
if chosen is not None or rotation["modified"]:
prior_p = start; prior_r = current[h][:3, :3]
for i in range(n):
retained = fresh[i] if chosen is None else fresh[i] + additions[i]*direction
output[i, h*7:h*7+3] = prior_r.T @ (retained-prior_p)
target_r=rotation["rotations"][i]
if rotation["modified"]:
q=quaternion(prior_r.T@target_r)
if q@raw[i,h*7+3:h*7+7]<0:q=-q
output[i,h*7+3:h*7+7]=q
prior_p, prior_r = retained, target_r
modified = chosen is not None and best>0
reports[side] = dict(
blended=False, translation_retained=modified, grasp_by_width=held[h],
measured_width_rad=float(measured[h]), previous_applied_command_rad=float(previous[h]),
width_threshold_rad=ARM_POLICY["grasp_width_min_rad"], postgrasp_executed_rows=counts[h],
reset_reasons=reasons[h], retention_phase_complete=done[h],
reference_hand=SIDES[1-h], reference_assumed_stationary=stable[h],
direction_frame="opposite hand at grasp, assumed stationary",
correction_direction=None if direction is None else direction.tolist(),
correction_direction_hand=None if direction is None else (current[h][:3, :3].T @ direction).tolist(),
pending_old_forward_forecast=pending_forward,
old_forward_peak_m=old_peak, fresh_forward_peak_m=fresh_peak,
old_sources=[] if chosen is None else [dict(generation=chosen[0]["generation"],
age_chunks=self.generation-chosen[0]["generation"], consumed_rows=chosen[1])],
added_forward_m=additions.tolist() if chosen is not None else [],
max_target_shift_m=best, **rotation["report"])
assert np.array_equal(output[:, 14:], raw[:, 14:])
for h in range(2):
if not reports[SIDES[h]]["rotation_retained"]:
assert np.array_equal(output[:, h*7+3:h*7+7], raw[:, h*7+3:h*7+7])
assert np.array_equal(output[n:], raw[n:])
report = dict(enabled=True, policy=self.policy, generation=self.generation,
executed_row_clock=self.clock, modified=not np.array_equal(output, raw),
hands=reports, retained_prefix_rows=n, committed=False,
future_rows="unchanged raw predictions" if self.n else "finite old-tail overlap only")
return dict(actions=output, report=report, generation=self.generation,
poses=poses, grips=raw[:, 14:].copy(), selected=selected.copy(),
reset=resets, held=held, held_rows=counts, directions=directions, phase_done=done,
rotation_states=rotation_states,
relative_path=self._relative_path(relative, output))
def _rotation_proposal(self,h,current,poses,selected,held,reset,raw_grips):
n=self.n or 32;fresh=poses[:n,h,:3,:3]
carry=held and self.held_last[h]
base=self.rotation_bases[h] if carry else current[h][:3,:3].copy() if held else None
axis=self.rotation_axes[h] if carry else None
complete=self.rotation_retention_done[h] if carry else False
previous=self.rotation_progress[h] if carry else 0.
state=dict(base=base,axis=axis,done=complete,progress=previous)
report=dict(rotation_retained=False,rotation_phase_complete=complete,
rotation_axis_frame="opposite hand, assumed stationary",rotation_axis=None,
rotation_progress_rad=previous,added_rotation_rad=[],rotation_old_sources=[],
max_target_rotation_change_rad=0.,rotation_reset_reasons=[])
result=dict(rotations=fresh.copy(),modified=False,state=state,report=report)
if not held:return result
if complete:
report["rotation_reset_reasons"].append("postgrasp_rotation_phase_complete")
return result
closed_end=next((i for i,v in enumerate(raw_grips) if regime(v)!="closed"),32)
if axis is None:
candidates=[quaternion(r@base.T) for r in poses[:closed_end,h,:3,:3]]
if candidates:
q=max(candidates,key=lambda q:2*np.arctan2(np.linalg.norm(q[:3]),q[3]))
magnitude=2*np.arctan2(np.linalg.norm(q[:3]),q[3])
if magnitude>=ARM_POLICY["rotation_axis_min_angle_rad"]:
axis=q[:3]/np.linalg.norm(q[:3]);state["axis"]=axis
if axis is None:
report["rotation_reset_reasons"].append("waiting_for_rotation_forecast_axis")
return result
report["rotation_axis"]=axis.tolist()
try:
present=twist_angle(current[h][:3,:3]@base.T,axis,previous)
fresh_angles=twist_path(poses[:,h,:3,:3],base,axis,present)
state["progress"]=present;report["rotation_progress_rad"]=present
best=0.;chosen=None;additions=np.zeros(n);pending=False
old_peak=0.;epsilon=ARM_POLICY["rotation_progress_epsilon_rad"]
for entry in self.history:
offset=self.clock-entry["birth"]
if reset or not entry["valid"][h] or not 1<=offset<32:continue
length=min(n,32-offset)
if self.n and length!=n:continue
mismatch=next((i for i in range(length) if regime(entry["grips"][offset+i,h])!=regime(selected[i,h])
or regime(selected[i,h])!="closed"),length)
if self.n and mismatch!=length:continue
length=mismatch
if not length:continue
old_angles=twist_path(entry["poses"][:,h,:3,:3],base,axis,present)
progress=old_angles[offset:offset+length]-present
peak=float(np.max(progress));old_peak=max(old_peak,peak)
if peak<=epsilon:continue
pending=True;delta=np.zeros(n)
delta[:length]=np.maximum(np.maximum(progress,0.)-(fresh_angles[:length]-present),0.)*self.scale
amount=float(np.max(delta))
if amount>best:best,chosen,additions=amount,(entry,offset),delta
fresh_peak=max(0.,float(np.max(fresh_angles[:closed_end]-present))) if closed_end else 0.
end=min(n,closed_end)
if not pending and fresh_peak<=epsilon and end and fresh_angles[end-1]-present<=epsilon:
state["done"]=True;report["rotation_phase_complete"]=True
report["rotation_reset_reasons"].append("no_unexecuted_rotation_forecast; return_to_latest_model")
report.update(pending_old_rotation_forecast=pending,old_rotation_peak_rad=old_peak,
fresh_rotation_peak_rad=fresh_peak)
if chosen is not None:
if best>=np.pi:raise ValueError("Rotation correction reaches ambiguous half-turn")
blend_rows=self.n or self.observed_execution_rows or 16
t=np.minimum(np.arange(1,n+1,dtype=float)/blend_rows,1.)
unramped=additions.copy()
additions=additions*(3*t*t-2*t*t*t)
for i in range(n):result["rotations"][i]=axis_rotation(axis,additions[i])@fresh[i]
result["modified"]=True
report.update(rotation_retained=True,added_rotation_rad=additions.tolist(),
unramped_added_rotation_rad=unramped.tolist(),rotation_blend_rows=blend_rows,
max_target_rotation_change_rad=float(np.max(additions)),rotation_old_sources=[dict(
generation=chosen[0]["generation"],age_chunks=self.generation-chosen[0]["generation"],consumed_rows=chosen[1])])
except ValueError as exc:
state["done"]=True;report["rotation_phase_complete"]=True
report["rotation_reset_reasons"].append(str(exc))
return result
@staticmethod
def _relative_path(relative, output):
p = forecast(output, [np.eye(4), relative])
return np.asarray([inverse(row[0]) @ row[1] for row in p])
def commit(self, proposal, executed_rows, applied=None, *, verified=True):
if proposal["generation"] != self.generation:
raise ValueError("Stale grasp-axis proposal")
limit = self.n or 32
if type(executed_rows) is not int or not 0 <= executed_rows <= limit:
raise ValueError("Invalid executed row count")
if not executed_rows or (self.n and executed_rows != self.n):
self.history.clear(); self.held_last = [False, False]; self.held_rows = [0, 0]
self.grasp_directions = [None, None]; self.retention_done = [False, False]
self.rotation_bases = [None, None]; self.rotation_axes = [None, None]
self.rotation_retention_done = [False, False]; self.rotation_progress = [0., 0.]
self.clock += executed_rows; self.generation += 1
proposal["report"].update(committed=True, executed_rows=executed_rows, history_reset="shortened_prefix")
return
end = [regime(v) for v in proposal["selected"][executed_rows-1]]
valid = [x == "closed" and proposal["report"]["hands"][side]["reference_assumed_stationary"]
for side,x in zip(SIDES,end)]
if applied is not None:
actual = pair(applied)
valid = [v and regime(a) == e for v, a, e in zip(valid, actual, end)]
reset = list(proposal["reset"])
if self.n is None:
for h in range(2):
if any(regime(x) != "closed" for x in proposal["selected"][:executed_rows, h]):
reset[h] = True; proposal["held"][h] = False
kept = []
for entry in self.history:
entry = dict(entry, valid=[a and b and not r and h for a,b,r,h in
zip(entry["valid"], valid, reset, proposal["held"])])
if any(entry["valid"]) and self.clock+executed_rows-entry["birth"] < 32:
kept.append(entry)
kept.append(dict(birth=self.clock, generation=self.generation,
poses=proposal["poses"].copy(), grips=proposal["grips"].copy(), valid=valid))
self.history = [e for e in kept if any(e["valid"])]
self.held_last = [h and v for h,v in zip(proposal["held"], valid)]
self.held_rows = [c+executed_rows if h else 0 for c,h in zip(proposal["held_rows"],self.held_last)]
self.grasp_directions = [None if not h or d is None else d.copy()
for h,d in zip(self.held_last,proposal["directions"])]
self.retention_done = [bool(h and d) for h,d in zip(self.held_last,proposal["phase_done"])]
self.rotation_bases=[s["base"].copy() if held and s["base"] is not None else None
for held,s in zip(self.held_last,proposal["rotation_states"])]
self.rotation_axes=[s["axis"].copy() if held and s["axis"] is not None else None
for held,s in zip(self.held_last,proposal["rotation_states"])]
self.rotation_retention_done=[bool(held and s["done"]) for held,s in zip(self.held_last,proposal["rotation_states"])]
self.rotation_progress=[s["progress"] if held else 0. for held,s in zip(self.held_last,proposal["rotation_states"])]
self.clock += executed_rows; self.generation += 1
proposal["report"].update(committed=True, executed_rows=executed_rows,
endpoint_verified=verified, commit_boundary="verified_client_execution" if verified else "observed_relative_pose_alignment",
postgrasp_executed_rows_after=dict(zip(SIDES,self.held_rows)),
history_generations=[e["generation"] for e in self.history])
def _slow_half_speed(actions):
out = np.repeat(actions, 2, axis=0)
for start in (0, 7):
rotation = Rotation.from_quat(actions[:, start+3:start+7])
half = Rotation.from_rotvec(rotation.as_rotvec() * .5)
translation = actions[:, start:start+3] * .5
out[0::2, start:start+3] = translation
out[1::2, start:start+3] = half.inv().apply(translation)
out[0::2, start+3:start+7] = half.as_quat(canonical=True)
out[1::2, start+3:start+7] = half.as_quat(canonical=True)
return out.astype(np.float32)
def _pa1_pick_compression(actions, measured):
measured = np.asarray(measured, dtype=float)
if measured[1] <= .56 or np.max(np.abs(actions[0, 14:16]-measured)) > .05:
return None
if np.max(np.ptp(actions[:, 14:16], axis=0)) > .03 or np.min(actions[:, 15]) < .56:
return None
distances = np.stack([np.linalg.norm(actions[:, k:k+3], axis=1) for k in (0, 7)], axis=1)
angles = np.stack([Rotation.from_quat(actions[:, k+3:k+7]).magnitude() for k in (0, 7)], axis=1)
if np.max(distances[:4].sum(axis=0)) >= .002 or np.max(angles[:4].sum(axis=0)) >= np.deg2rad(1.):
return None
groups, counts = [], []
index = 0
while index < len(actions) and len(groups) < 8:
end = index+1
length = distances[index].copy()
angle = angles[index].copy()
while end < min(index+8, len(actions)):
next_length = length+distances[end]
next_angle = angle+angles[end]
if np.max(next_length) > .002 or np.max(next_angle) > np.deg2rad(.5):
break
length, angle = next_length, next_angle
end += 1
combined = actions[end-1].astype(np.float64).copy()
for start in (0, 7):
position = np.zeros(3)
rotation = Rotation.identity()
for row in actions[index:end]:
position += rotation.apply(row[start:start+3])
rotation = rotation*Rotation.from_quat(row[start+3:start+7])
combined[start:start+3] = position
combined[start+3:start+7] = rotation.as_quat(canonical=True)
groups.append(combined)
counts.append(end-index)
index = end
consumed = sum(counts[:4])
if consumed <= 4:
return None
while len(groups) < 8:
hold = groups[-1].copy()
hold[:14] = np.tile([0., 0., 0., 0., 0., 0., 1.], 2)
groups.append(hold)
return _slow_half_speed(np.asarray(groups, dtype=np.float32)), consumed
def _cup_tip_pivot(actions, column, release_row, hold_angle):
out = actions.copy()
opening = 2*(release_row-1)
chunk = opening//8*8
stop = max(opening, chunk+4)
start = stop-4
delay = stop-opening
if delay:
out[stop:, column] = actions[opening:len(actions)-delay, column]
out[start:stop, column] = hold_angle
rotation = Rotation.from_rotvec([0., -np.deg2rad(10.)/4., 0.])
pivot = np.array([.09, 0., 0.])
translation = pivot-rotation.apply(pivot)
offset = 0 if column == 14 else 7
out[start:stop, offset:offset+3] = translation
out[start:stop, offset+3:offset+7] = rotation.as_quat(canonical=True)
return out
def _pen_preclose_approach(actions, measured):
closes = np.flatnonzero(actions[:16, 15] < .07)
if not len(closes) or measured[1] < .07:
return None
first = int(closes[0])
stop = max(first, 4)
start = stop-4
delay = stop-first
out = actions.copy()
if delay:
out[stop:, 15] = actions[first:len(actions)-delay, 15]
out[start:stop, 15] = measured[1]
rotation = Rotation.identity()
for i in range(start, stop):
out[i, 7:10] += rotation.inv().apply([.005/4., 0., 0.])
rotation = rotation*Rotation.from_quat(out[i, 10:14])
return out
def _pen_extra_lift(actions, direction):
out = actions.copy()
rotation = Rotation.identity()
for i in range(16):
out[i, 7:10] += rotation.inv().apply(np.asarray(direction) * .0025)
rotation = rotation * Rotation.from_quat(out[i, 10:14])
return out
def _pa2_initial_lift_direction(actions):
position = np.zeros(3)
rotation = Rotation.identity()
for row in actions[:8]:
if row[14] >= .56:
break
position += rotation.apply(row[:3])
rotation = rotation * Rotation.from_quat(row[3:7])
length = np.linalg.norm(position)
return position / length if length > 1e-6 else None
def _pa2_scale_lift(actions, direction, release_row):
out = actions.copy()
rotation = Rotation.identity()
limit = min(16, 2 * (release_row - 1)) if release_row is not None else 16
finished = False
for i in range(limit):
if out[i, 14] >= .56:
finished = True
break
projected = float(rotation.apply(out[i, :3]) @ direction)
if projected < -1e-5:
finished = True
break
if projected > 0:
out[i, :3] += rotation.inv().apply(.5 * projected * direction)
rotation = rotation * Rotation.from_quat(out[i, 3:7])
return out, finished or release_row is not None
class Policy:
def __init__(self, checkpoint_dir: str):
from openpi.models import pi0_config
from openpi.policies import policy_config
from openpi.training.config import AssetsConfig, DataConfig, TrainConfig
checkpoint = pathlib.Path(checkpoint_dir)
if not (checkpoint / "params").is_dir():
checkpoint = checkpoint / "step_042000"
config = TrainConfig(
name="u1_42000_submission",
model=pi0_config.Pi0Config(pi05=True, action_dim=32, action_horizon=32),
data=_build_transforms()(
assets=AssetsConfig(asset_id=_asset_id(checkpoint)),
base_config=DataConfig(prompt_from_task=True)),
)
self._model = policy_config.create_trained_policy(config, str(checkpoint))
self.reset()
rng = self._model._rng
image = np.random.default_rng(0).integers(0, 256, (480, 640, 3), dtype=np.uint8)
obs = {_K[0]: image, _K[1]: image.copy(),
POSE_KEY: np.array([.23, -.05, .27, -.58, -.09, .72, .34], np.float32),
_K[3]: np.array([.24, .78], np.float32)}
started = time.monotonic()
try:
for prompt in (PEN_PROMPTS[0], CUP_PA1, CUP_PA2):
self._sample(dict(obs, prompt=prompt), prompt)
finally:
self._model._rng = rng
self.reset()
print(f"[policy] pre-warmed in {time.monotonic()-started:.1f}s", flush=True)
def reset(self):
self._prompt = None
self._second_half = None
self._pending = None
self._cycle = 0
self._previous_grip = None
self._cup = None
self._cup_seen_open = False
self._cup_grasped = False
self._cup_tilted = False
self._pa2_lift_axis = None
self._pa2_lift_done = False
self._pen_approach_done = False
self._pen_lift_added = False
self._pen = PenGraspSchedule(8, close_defer_only=True)
self._arm = GraspAxisRetention(execution_rows=8, correction_scale=1.0)
def _sample(self, obs, prompt):
clean = {key: obs[key] for key in _K if key in obs}
mapped = prompt
if prompt.startswith("Pick 2nd ") and " part and place into cell" in prompt:
mapped = "Pick 1st " + prompt[len("Pick 2nd "):]
clean["prompt"] = mapped
sigma = CUP_NOISE.get(prompt, 1.0)
kwargs = {}
if sigma != 1.0:
import jax
_, key = jax.random.split(self._model._rng)
noise = jax.random.normal(key, (1, 32, 32))
kwargs["noise"] = noise * sigma
actions = np.asarray(self._model.infer(clean, **kwargs)["actions"], dtype=np.float32)
if actions.shape != (32, 16) or not np.isfinite(actions).all():
raise ValueError("Expected finite float32 model actions [32,16]")
return actions.copy()
def _finish_previous_pair(self):
if self._pending is None:
return
kind, proposals, applied = self._pending
if kind == "cup":
self._cup.commit(proposals, proposals['adopt_rows'])
else:
transition, arm = proposals
self._pen.commit(transition, executed_rows=8)
self._arm.commit(arm, 8, applied=applied, verified=False)
self._previous_grip = applied.copy()
self._pending = None
self._cycle += 1
def infer(self, obs: dict) -> np.ndarray:
prompt = obs.get("prompt", "").strip().rstrip(".")
if prompt != self._prompt:
self.reset()
self._prompt = prompt
if prompt in CUP_HANDS:
self._cup = CupContinuousAdvance(CUP_ADVANCE[prompt])
if self._second_half is not None:
result = self._second_half
self._second_half = None
return result.copy()
self._finish_previous_pair()
raw = self._sample(obs, prompt)
tip_release = None
lift_direction = None
if prompt in CUP_HANDS:
finger = float(obs[_K[3]][CUP_HANDS[prompt][1]-14])
if finger > .53:
self._cup_seen_open = True
elif self._cup_seen_open and finger >= 3*2*np.pi/4096:
self._cup_grasped = True
if prompt == CUP_PA1 and not self._cup_grasped:
compressed = _pa1_pick_compression(raw, obs[_K[3]])
if compressed is not None:
output, consumed = compressed
proposal = self._cup.propose(raw, dict(obs, prompt=prompt), consumed)
self._pending = ('cup', proposal, None)
return output.copy()
proposal = self._cup.propose(raw, dict(obs, prompt=prompt), 8)
controlled = proposal["actions"]
self._pending = ("cup", proposal, None)
tip_release = proposal["report"]["forced_start_row"] or proposal["report"]["natural_start_row"]
elif prompt in PEN_PROMPTS:
measured = np.asarray(obs[_K[3]], dtype=float)
if measured[1] >= .27 and np.any(raw[:8, 15] >= .27):
self._pen_approach_done = False
self._pen_lift_added = False
limits = {side: dict(min_angle_rad=-1e10, max_angle_rad=1e10) for side in SIDES}
targets, transition = self._pen.propose(
self._cycle, raw, raw[:8, 14:16], measured, {}, limits,
allow_clipping=False)
arm = self._arm.propose(raw, obs, targets,
previous_applied=self._previous_grip)
hand = arm["report"]["hands"]["right"]
if not self._pen_lift_added and hand["translation_retained"]:
lift_direction = hand["correction_direction_hand"]
controlled = arm["actions"].copy()
controlled[:8, 14:16] = targets
self._pending = ("pen", (transition, arm), targets[-1].copy())
else:
controlled = raw
slow = _slow_half_speed(controlled)
if prompt == CUP_PA2 and self._cup_grasped and not self._pa2_lift_done:
relative_rotation = Rotation.from_quat(np.asarray(obs[POSE_KEY])[3:])
if self._pa2_lift_axis is None:
direction = _pa2_initial_lift_direction(slow)
if direction is not None:
self._pa2_lift_axis = relative_rotation.inv().apply(direction)
if self._pa2_lift_axis is not None:
direction = relative_rotation.apply(self._pa2_lift_axis)
slow, self._pa2_lift_done = _pa2_scale_lift(slow, direction, tip_release)
if lift_direction is not None:
slow = _pen_extra_lift(slow, lift_direction)
self._pen_lift_added = True
if prompt in PEN_PROMPTS and not self._pen_approach_done:
approached = _pen_preclose_approach(slow, np.asarray(obs[_K[3]], dtype=float))
if approached is not None:
slow = approached
self._pen_approach_done = True
if tip_release is not None and self._cup_grasped and not self._cup_tilted:
column = CUP_HANDS[prompt][1]
slow = _cup_tip_pivot(slow, column, tip_release, float(obs[_K[3]][column-14]))
self._cup_tilted = True
self._second_half = slow[8:24].copy()
return slow[:16].copy()
from copy import deepcopy
import dataclasses
import io
import os
import pathlib
import time
import numpy as np
from PIL import Image
from scipy.spatial.transform import Rotation
os.environ.setdefault("XLA_PYTHON_CLIENT_PREALLOCATE", "false")
JPEG_QUALITY = 90
SIDES = ("left", "right")
POSE_KEY = "observation.pose.left_hand_root_to_right_hand_root.absolute"
_K = ("observation.image.left", "observation.image.right", POSE_KEY,
"observation.joint_states", "prompt")
CUP_PA1 = "Pick up the cup with your right hand and set it on the plate"
CUP_PA2 = "Pick up the cup with your left hand and return the cup to its original position"
CUP_HANDS = {CUP_PA1: ("right", 15), CUP_PA2: ("left", 14)}
CUP_ADVANCE = {CUP_PA1: 8, CUP_PA2: 4}
CUP_NOISE = {CUP_PA1: .15, CUP_PA2: .10}
PEN_PROMPTS = (
"Pick up a pencil on the table and put it into the pen holder",
"Pick up a ballpoint pen on the table and put it into the pen holder",
)
def _asset_id(ckpt):
found = sorted(p.parent.name for p in (ckpt / "assets").glob("*/norm_stats.json"))
if len(found) != 1:
raise FileNotFoundError(f"expected one assets/<id>/norm_stats.json in {ckpt}, found {found}")
return found[0]
def _m(r):
a, b = r[..., :3], r[..., 3:6]
c0 = a / np.linalg.norm(a, axis=-1, keepdims=True)
b = b - np.sum(c0 * b, axis=-1, keepdims=True) * c0
c1 = b / np.linalg.norm(b, axis=-1, keepdims=True)
return np.stack([c0, c1, np.cross(c0, c1)], axis=-1)
def _d(x):
rot = Rotation.from_matrix(_m(x[:, 3:9]))
pr = Rotation.concatenate([Rotation.identity(), rot[:-1]])
pp = np.concatenate([np.zeros((1, 3)), x[:-1, :3]])
return np.concatenate([pr.inv().apply(x[:, :3] - pp), (pr.inv() * rot).as_quat(canonical=True)], axis=1)
def _image(img):
from openpi.shared import image_tools
a = np.asarray(image_tools.resize_with_pad(np.asarray(img, dtype=np.uint8), 224, 224))
b = io.BytesIO(); Image.fromarray(a).save(b, format="JPEG", quality=JPEG_QUALITY)
return np.asarray(Image.open(io.BytesIO(b.getvalue())).convert("RGB"))
def _build_transforms():
from openpi import transforms as T
from openpi.training.config import DataConfigFactory, ModelTransformFactory
class _P(T.DataTransformFn):
def __call__(self, d):
return {k: d[k] for k in _K if k in d}
class _I(T.DataTransformFn):
n: int = 32
def __call__(self, d):
l, r = _image(d[_K[0]]), _image(d[_K[1]])
p = np.array(d[_K[2]], dtype=np.float64)
if p[6] < 0:
p[3:] *= -1
s = np.concatenate([p, np.asarray(d[_K[3]], dtype=np.float64)]).astype(np.float32)
o = {"state": T.pad_to_dim(s, self.n),
"image": {"base_0_rgb": np.zeros_like(l), "left_wrist_0_rgb": l, "right_wrist_0_rgb": r},
"image_mask": {"base_0_rgb": np.False_, "left_wrist_0_rgb": np.True_, "right_wrist_0_rgb": np.True_}}
if "prompt" in d:
o["prompt"] = d["prompt"]
return o
class _O(T.DataTransformFn):
def __call__(self, d):
a = np.asarray(d["actions"], dtype=np.float64)
return {"actions": np.concatenate([_d(a[:, 0:9]), _d(a[:, 9:18]), a[:, 18:20]], axis=1).astype(np.float32)}
class _C(DataConfigFactory):
def create(self, assets_dirs, model_config):
return dataclasses.replace(
self.create_base_config(assets_dirs, model_config),
repack_transforms=T.Group(inputs=[_P()]),
data_transforms=T.Group(inputs=[_I(model_config.action_dim)], outputs=[_O()]),