API reference¶
The getting started guide shows a recording, and the CLI reference lists the commands. This page is the Python API.
Library and recording¶
Library.rename(old, new) renames a rollout. The folder and the name in
rollout.json change. The id, annotations, groups, and derived cache stay.
It refuses a rollout that is still recording, and it refuses a name that
already exists. Reopen a Rollout that was opened before the rename. The
viewer does the same thing through POST /api/runs/<name>/rename.
simscope.library
¶
A simscope library: a folder of recorded rollouts.
Library(root) is a cheap handle. The folder layout is
created lazily, by the first recording. lib.record returns a
:class:~simscope.recorder.Recorder, lib.open a :class:Rollout, and
lib.query searches the SQLite index cache.
Library
¶
A folder of rollouts.
Attributes:
| Name | Type | Description |
|---|---|---|
root |
The library folder. |
Source code in src/simscope/library.py
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 | |
runs_dir: pathlib.Path
property
¶
The runs/ folder.
__init__(root: os.PathLike[str] | str) -> None
¶
Creates a handle. Nothing is created on disk yet.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
PathLike[str] | str
|
The library folder; it and its layout are created by the first recording. |
required |
Source code in src/simscope/library.py
353 354 355 356 357 358 359 360 361 | |
close() -> None
¶
Closes the index connection (it reopens on demand).
Source code in src/simscope/library.py
606 607 608 | |
count(text: str | None = None, tags: Iterable[str] = (), favorite: bool | None = None, status: str | None = None, *, refresh: bool = True) -> int
¶
Counts runs matching the filters of :meth:query.
Source code in src/simscope/library.py
592 593 594 595 596 597 598 599 600 601 602 603 604 | |
event_types() -> dict[str, annotations.EventType]
¶
Returns the event vocabulary (the built-in one if unset).
Source code in src/simscope/library.py
612 613 614 | |
open(name: str) -> Rollout
¶
Opens a run for reading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Run name. |
required |
Returns:
| Type | Description |
|---|---|
Rollout
|
The rollout. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If there is no such run. |
FormatError
|
If its manifest is invalid. |
ValueError
|
If the name is invalid. |
Source code in src/simscope/library.py
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 | |
query(text: str | None = None, tags: Iterable[str] = (), favorite: bool | None = None, status: str | None = None, sort: str = 'created', descending: bool = True, limit: int | None = 50, offset: int = 0, *, refresh: bool = True) -> list[RunInfo]
¶
Searches the index. See :meth:simscope.index.Index.query.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str | None
|
Terms that must all appear in name, tags, group or notes. |
None
|
tags
|
Iterable[str]
|
Record-time tags a run must have (all of them). |
()
|
favorite
|
bool | None
|
Only runs with this favorite mark. |
None
|
status
|
str | None
|
Manifest status to match ( |
None
|
sort
|
str
|
|
'created'
|
descending
|
bool
|
Sort direction. |
True
|
limit
|
int | None
|
Page size, or |
50
|
offset
|
int
|
Rows to skip. |
0
|
refresh
|
bool
|
Update the index first. |
True
|
Returns:
| Type | Description |
|---|---|
list[RunInfo]
|
The matching page of run summaries. The |
list[RunInfo]
|
that is still recording counts the windows written so far, since |
list[RunInfo]
|
the recorder rewrites its partial manifest after each window. |
Source code in src/simscope/library.py
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 | |
record(name: str, *, scene: core.Scene, dt: float, n_envs: int = 1, env_origins: npt.ArrayLike | None = None, source: Mapping[str, Any] | None = None, tags: Sequence[str] = (), meta: Mapping[str, Any] | None = None, codec: str = 'f32s', block_frames: int = blockfile.DEFAULT_BLOCK_FRAMES, overwrite: bool = False, encode_threads: int | None = None, created: str | None = None) -> recorder.Recorder
¶
Starts recording a run.
Use the result as a context manager, calling add_stream for
extra streams and log once per step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Run name, |
required |
scene
|
Scene
|
The static scene. |
required |
dt
|
float
|
Seconds between frames. |
required |
n_envs
|
int
|
Number of parallel envs. |
1
|
env_origins
|
ArrayLike | None
|
World offset per env, |
None
|
source
|
Mapping[str, Any] | None
|
Provenance, for example from |
None
|
tags
|
Sequence[str]
|
Record-time tags. |
()
|
meta
|
Mapping[str, Any] | None
|
Free-form JSON metadata. |
None
|
codec
|
str
|
Pose codec, |
'f32s'
|
block_frames
|
int
|
Frames per block. |
DEFAULT_BLOCK_FRAMES
|
overwrite
|
bool
|
Replace an existing run (deletes its folder). |
False
|
encode_threads
|
int | None
|
Threads the writer spreads per-env encoding over;
|
None
|
created
|
str | None
|
Recording time (UTC, |
None
|
Returns:
| Type | Description |
|---|---|
Recorder
|
The recorder. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an argument is invalid. |
FileExistsError
|
If the run exists and |
Source code in src/simscope/library.py
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 | |
recover(name: str) -> RecoverReport
¶
Repairs a run whose recording crashed.
Every block file is recovered (complete windows kept, the rest cut
off), streams are aligned to the shortest one, and the manifest is
rewritten as "complete". A finished run is left untouched.
Do not call it while a recorder is still writing the run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Run name. |
required |
Returns:
| Type | Description |
|---|---|
RecoverReport
|
What was kept. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the run or its manifest is missing. |
FormatError
|
If the manifest is invalid. |
Source code in src/simscope/library.py
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 | |
refresh() -> index.RefreshStats
¶
Rescans rollout libraries under this folder and updates the index.
A refresh is incremental: runs whose files are unchanged are not re-read. Libraries nested under this folder are included.
Source code in src/simscope/library.py
534 535 536 537 538 539 540 | |
rename(old: str, new: str) -> None
¶
Renames a run.
A run is known by its folder name and the name in its manifest;
its id never changes, so annotations, groups, the derived cache
and exports keep belonging to it. Only rollout.json is
rewritten (atomically); streams, scenes and assets are untouched.
If the folder cannot be renamed, the manifest is put back.
:class:Rollout objects opened before the rename keep pointing at
the old folder; reopen the run with :meth:open.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
old
|
str
|
The run's current name. |
required |
new
|
str
|
The new name, |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a name is invalid or the run is still recording. |
FileNotFoundError
|
If there is no run |
FileExistsError
|
If a run |
OSError
|
If the folder cannot be renamed (for example on Windows while a file in it is open). |
FormatError
|
If the manifest is invalid. |
Source code in src/simscope/library.py
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 | |
run_dir(name: str) -> pathlib.Path
¶
Returns a run's folder (which may not exist).
A run in runs/ of this folder is that directory. Otherwise the
folder is the one discovered under a nested library. When nothing
matches, the path is runs/<name> of this folder, which is where
a new recording would go.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the name is invalid. |
Source code in src/simscope/library.py
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | |
runs(refresh: bool = True) -> list[RunInfo]
¶
Lists every run, newest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refresh
|
bool
|
Update the index first (cheap when nothing changed). |
True
|
Returns:
| Type | Description |
|---|---|
list[RunInfo]
|
One summary per run. |
Source code in src/simscope/library.py
542 543 544 545 546 547 548 549 550 551 | |
set_event_types(types: Mapping[str, annotations.EventType] | Iterable[annotations.EventType]) -> None
¶
Writes .simscope/event_types.json.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
types
|
Mapping[str, EventType] | Iterable[EventType]
|
Entries by id, or an iterable of entries (keyed by their
|
required |
Source code in src/simscope/library.py
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 | |
RecoverReport
dataclass
¶
What :meth:Library.recover did.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Run name. |
n_frames |
int
|
Frames in the recovered run. |
already_complete |
bool
|
True if the run was finished and left untouched. |
streams |
Mapping[str, int]
|
Frames each stream held before they were aligned. |
dropped_frames |
int
|
Frames removed from streams that were ahead of the shortest one (a crash between two streams' writes). |
Source code in src/simscope/library.py
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | |
Rollout
¶
One recorded run, opened for reading.
Block files open lazily and are memory-mapped; nothing is decoded until
a window is read. Close the rollout (or use with) to release them.
A run that is still recording (rollout.json.partial) can be tailed:
call :meth:refresh to pick up the windows the recorder has finished
since. Only complete windows count, so n_frames never includes a
window that is half written. When the recorder finishes, refresh
switches to the final manifest and is_recording becomes false.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
Run name. |
|
path |
Run directory. |
|
manifest |
The parsed |
Source code in src/simscope/library.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | |
annotations: annotations.Annotations
property
¶
The run's curation sidecar (loaded once; call save()).
dt: float
property
¶
Seconds between frames.
env_origins: npt.NDArray[np.float64]
property
¶
World offset of each env, [n_envs, 3] (zeros if unset).
is_recording: bool
property
¶
True while the recorder has not finished the run.
It reflects the manifest as of the last :meth:refresh. A run whose
recorder crashed stays "recording" until :meth:Library.recover.
n_envs: int
property
¶
Envs per stream.
n_frames: int
property
¶
Frames per stream.
For a recording run this is the count found by the latest
:meth:refresh (0 before the first one).
scene: core.Scene
property
¶
The scene, loaded from the content store on first use.
__enter__() -> Rollout
¶
Returns the rollout.
Source code in src/simscope/library.py
312 313 314 | |
__exit__(exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None
¶
Closes the rollout.
Source code in src/simscope/library.py
316 317 318 319 320 321 322 323 | |
__init__(root: pathlib.Path, name: str) -> None
¶
Reads the manifest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
The library root. |
required |
name
|
str
|
Run name. |
required |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the run does not exist. |
FormatError
|
If the manifest is invalid. |
Source code in src/simscope/library.py
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
close() -> None
¶
Releases every open block file.
Source code in src/simscope/library.py
305 306 307 308 309 310 | |
frame_source(stream: str = 'body_pose') -> _PoseFrameSource
¶
Returns a viewer-ready source of pose frames.
For a recording run this refreshes the rollout first, and the
source's n_frames then follows each :meth:refresh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stream
|
str
|
A pose stream; |
'body_pose'
|
Returns:
| Type | Description |
|---|---|
_PoseFrameSource
|
An object with |
_PoseFrameSource
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the stream is not a pose stream. |
Source code in src/simscope/library.py
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
refresh() -> int
¶
Picks up what the recorder has written since the last call.
Re-reads the manifest (rollout.json.partial, or the final
rollout.json once the run is complete) and refreshes every stream
reader. The cost is proportional to the new data. On a run that was
complete when opened this does nothing.
Returns:
| Type | Description |
|---|---|
int
|
Frames readable in every stream: the minimum across streams while |
int
|
recording, and the manifest's count once complete. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the manifest or a block file is corrupt. |
Source code in src/simscope/library.py
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
reload_annotations() -> annotations.Annotations
¶
Re-reads the curation sidecar from disk, dropping unsaved edits.
Returns:
| Type | Description |
|---|---|
Annotations
|
The freshly loaded annotations, which :attr: |
Annotations
|
from then on. |
Source code in src/simscope/library.py
286 287 288 289 290 291 292 293 294 | |
stream(name: str, *, live: bool = False) -> blockfile.BlockReader
¶
Returns the reader of a stream, opening it on first use.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Stream name from the manifest. |
required |
live
|
bool
|
Allow a stream of a recording run, tailing its file. It is
implied once :meth: |
False
|
Returns:
| Type | Description |
|---|---|
BlockReader
|
A shared |
BlockReader
|
recording run its |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the run has no such stream. |
FormatError
|
If the run is unfinished and not read with
|
Source code in src/simscope/library.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
simscope.recorder
¶
Crash-safe, low-overhead rollout recorder.
Recorder.log only validates cheaply and copies one frame into a
preallocated window buffer [block_frames, E, K] per stream. When a
window fills it is handed to one background thread that applies pose sign
continuity, encodes (deflate releases the GIL, so this overlaps with the
simulator) and appends the blocks to the .blk files. Every finished
window is flushed to the OS, so a crash loses at most the window in flight;
Library.recover restores the rest.
Recorder
¶
Records one rollout into a library.
Create it with Library.record. Use it as a context manager: leaving
the with block normally calls :meth:close (status "complete");
leaving it on an exception calls :meth:abort, which keeps the run
recoverable (rollout.json.partial). Not thread-safe: call log
from one thread.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
Run name. |
|
path |
Run directory. |
|
dt |
Seconds per frame. |
|
n_envs |
Envs per frame. |
|
block_frames |
Frames per block. |
Source code in src/simscope/recorder.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 | |
n_frames: int
property
¶
Frames logged so far.
stats: dict[str, float]
property
¶
Backpressure counters.
stalls counts the times log had to wait for the encoder,
stall_seconds the total wait, and close_seconds how long the
last close took.
__enter__() -> Recorder
¶
Returns the recorder.
Source code in src/simscope/recorder.py
770 771 772 | |
__exit__(exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None
¶
Closes on success; aborts (keeping the run recoverable) on error.
Source code in src/simscope/recorder.py
774 775 776 777 778 779 780 781 782 783 784 | |
__init__(root: pathlib.Path, name: str, *, scene: core.Scene, dt: float, n_envs: int = 1, env_origins: npt.ArrayLike | None = None, source: Mapping[str, Any] | None = None, tags: Sequence[str] = (), meta: Mapping[str, Any] | None = None, codec: str = 'f32s', block_frames: int = blockfile.DEFAULT_BLOCK_FRAMES, overwrite: bool = False, max_pending: int = 2, encode_threads: int | None = None, created: str | None = None) -> None
¶
Validates arguments. Nothing is written until the first log.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
The library root. |
required |
name
|
str
|
Run name, |
required |
scene
|
Scene
|
The static scene. Its body count fixes the pose shape. |
required |
dt
|
float
|
Seconds between frames (positive). |
required |
n_envs
|
int
|
Number of parallel envs. |
1
|
env_origins
|
ArrayLike | None
|
World offset of each env, shape |
None
|
source
|
Mapping[str, Any] | None
|
Provenance, such as |
None
|
tags
|
Sequence[str]
|
Record-time tags. |
()
|
meta
|
Mapping[str, Any] | None
|
Free-form JSON-serializable metadata. |
None
|
codec
|
str
|
Codec of the pose stream: |
'f32s'
|
block_frames
|
int
|
Frames per block. |
DEFAULT_BLOCK_FRAMES
|
overwrite
|
bool
|
Replace an existing run of this name. The whole run directory (including its annotations) is deleted when recording starts. |
False
|
created
|
str | None
|
When the run was recorded, |
None
|
max_pending
|
int
|
Full windows that may wait for the encoder before
|
2
|
encode_threads
|
int | None
|
Threads the background writer fans a window's
per-env blocks out to (deflate releases the GIL, so this
scales). |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If an argument is invalid. |
FileExistsError
|
If the run exists and |
Source code in src/simscope/recorder.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | |
abort() -> None
¶
Stops without finishing the run, keeping it recoverable.
Flushes what it can and leaves rollout.json.partial in place.
Never raises; use Library.recover to complete the run.
Source code in src/simscope/recorder.py
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 | |
add_stream(name: str, kind: core.StreamKind, item_shape: Sequence[int] = (), *, labels: Sequence[str] | None = None, units: str | None = None, codec: str = 'f32s', scale: float | None = None) -> None
¶
Declares an extra stream. Call before the first log.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Stream name; also the keyword given to |
required |
kind
|
StreamKind
|
How viewers draw it ( |
required |
item_shape
|
Sequence[int]
|
Shape of one env-frame item ( |
()
|
labels
|
Sequence[str] | None
|
Optional component names. |
None
|
units
|
str | None
|
Optional unit string. |
None
|
codec
|
str
|
|
'f32s'
|
scale
|
float | None
|
For |
None
|
Raises:
| Type | Description |
|---|---|
RecorderError
|
If recording already started. |
ValueError
|
If the name is taken or invalid, or the kind and shape do not fit. |
Source code in src/simscope/recorder.py
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | |
close() -> None
¶
Finishes the run: flushes, writes directories and the manifest.
Does nothing if no frame was logged (no run is created). Safe to call twice.
Raises:
| Type | Description |
|---|---|
Exception
|
A failure of the background writer. The files stay
recoverable ( |
Source code in src/simscope/recorder.py
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 | |
flush() -> None
¶
Waits until every full window is on disk.
Raises:
| Type | Description |
|---|---|
Exception
|
A failure of the background writer, re-raised. |
Source code in src/simscope/recorder.py
691 692 693 694 695 696 697 698 699 700 701 | |
log(poses: npt.ArrayLike, **streams: npt.ArrayLike) -> None
¶
Records one frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
poses
|
ArrayLike
|
Body poses |
required |
**streams
|
ArrayLike
|
One value per declared stream, each |
{}
|
Raises:
| Type | Description |
|---|---|
RecorderError
|
If the recorder is closed. |
ValueError
|
If a shape is wrong or a stream is missing or undeclared. The frame is not recorded. |
Exception
|
A failure of the background writer, re-raised. |
Source code in src/simscope/recorder.py
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 | |
log_frames(poses: npt.ArrayLike, **streams: npt.ArrayLike) -> None
¶
Records n frames at once, without a Python loop over frames.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
poses
|
ArrayLike
|
Body poses |
required |
**streams
|
ArrayLike
|
One array per declared stream, each
|
{}
|
Raises:
| Type | Description |
|---|---|
RecorderError
|
If the recorder is closed. |
ValueError
|
If a shape is wrong or a stream is missing or undeclared. Nothing is recorded. |
Exception
|
A failure of the background writer, re-raised. |
Source code in src/simscope/recorder.py
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 | |
RecorderError
¶
Bases: RuntimeError
Raised for lifecycle errors, such as logging to a closed recorder.
Source code in src/simscope/recorder.py
40 41 | |
simscope.index
¶
SQLite index cache over rollout.json and annotations.json.
The index lives at <library>/.simscope/index.sqlite. It is a disposable
cache (decision D8): the sidecars stay the source of truth, and a missing,
corrupt or out-of-date-schema file is dropped and rebuilt on the next scan.
Index.refresh finds every rollout library under the root, a direct
runs/ folder or one nested further down, and re-reads only runs whose
files changed.
INVALID = 'invalid'
module-attribute
¶
Status shown for a run whose rollout.json could not be read.
Signature = tuple[int, int, int, int, int]
module-attribute
¶
(m_mtime, m_size, m_partial, a_mtime, a_size) of a run.
Index
¶
The scan cache of one library.
Thread-safe: a lock serializes access to the SQLite connection. If the cache cannot be created on disk (for example a read-only library) it falls back to an in-memory database.
Attributes:
| Name | Type | Description |
|---|---|---|
root |
The library root. |
|
path |
The index file. |
Source code in src/simscope/index.py
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 | |
__init__(root: os.PathLike[str] | str) -> None
¶
Creates a handle. The database opens lazily.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
PathLike[str] | str
|
The library root. |
required |
Source code in src/simscope/index.py
271 272 273 274 275 276 277 278 279 280 281 | |
close() -> None
¶
Closes the connection. The index reopens on the next use.
Source code in src/simscope/index.py
348 349 350 351 352 353 | |
count(text: str | None = None, tags: Iterable[str] = (), favorite: bool | None = None, status: str | None = None) -> int
¶
Returns how many runs match the filters of :meth:query.
Source code in src/simscope/index.py
489 490 491 492 493 494 495 496 497 498 499 500 501 502 | |
get(name: str) -> RunInfo | None
¶
Returns one run's summary, or None if it is not indexed.
Source code in src/simscope/index.py
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 | |
owner(name: str, *, scan: bool = False) -> pathlib.Path | None
¶
Returns the library root that holds name, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Run name. |
required |
scan
|
bool
|
When the run is missing from the index, scan once. Further
calls do not scan again until :meth: |
False
|
Returns:
| Type | Description |
|---|---|
Path | None
|
The library folder recorded for that run. |
Path | None
|
is not indexed. This does not create an index just to answer, |
Path | None
|
unless |
Source code in src/simscope/index.py
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | |
owners() -> list[pathlib.Path]
¶
Returns nested library folders that hold indexed runs.
The index root itself is omitted: its runs live in its runs/.
Returns:
| Type | Description |
|---|---|
list[Path]
|
Those folders, in path order. |
Source code in src/simscope/index.py
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | |
query(text: str | None = None, tags: Iterable[str] = (), favorite: bool | None = None, status: str | None = None, sort: str = 'created', descending: bool = True, limit: int | None = 50, offset: int = 0) -> list[RunInfo]
¶
Returns matching runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str | None
|
Whitespace-separated terms; a run must contain every term (case-insensitive) in its name, tags, group or note text. |
None
|
tags
|
Iterable[str]
|
Record-time tags a run must have (all of them). |
()
|
favorite
|
bool | None
|
If set, only runs with this favorite mark. |
None
|
status
|
str | None
|
If set, only runs whose manifest status
( |
None
|
sort
|
str
|
|
'created'
|
descending
|
bool
|
Sort direction. |
True
|
limit
|
int | None
|
Maximum rows, or |
50
|
offset
|
int
|
Rows to skip. |
0
|
Returns:
| Type | Description |
|---|---|
list[RunInfo]
|
The page of runs. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/index.py
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 | |
rebuild() -> RefreshStats
¶
Drops every row and rescans all runs.
Source code in src/simscope/index.py
355 356 357 358 359 | |
refresh() -> RefreshStats
¶
Brings the index up to date with the rollouts under root.
Finds every library under the root, not only root/runs, then
stats each run's manifest and annotations file. Only runs whose
mtime, size or library changed are re-read. The database update
happens in one transaction.
Returns:
| Type | Description |
|---|---|
RefreshStats
|
Counts of added, updated, removed and unchanged runs. |
Source code in src/simscope/index.py
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 | |
RefreshStats
dataclass
¶
What a refresh did.
Attributes:
| Name | Type | Description |
|---|---|---|
added |
int
|
Runs seen for the first time. |
updated |
int
|
Runs whose files changed. |
removed |
int
|
Runs that vanished. |
unchanged |
int
|
Runs skipped because their stat signature matched. |
Source code in src/simscope/index.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
RunInfo
dataclass
¶
Summary of one rollout, as browsing needs it.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Run name (the directory name). |
id |
str
|
The run's ULID. |
created |
str
|
UTC creation time. |
status |
str
|
|
dt |
float
|
Seconds per frame. |
n_frames |
int
|
Frames per stream. |
n_envs |
int
|
Envs per stream. |
n_bodies |
int
|
Bodies per pose. |
scene_hash |
str
|
SHA-256 of the scene descriptor. |
favorite |
bool
|
Whether the run is starred. |
group |
str | None
|
Name of the group the run belongs to, or |
n_events |
int
|
Number of events. |
n_notes |
int
|
Number of notes. |
rating |
float | None
|
Mean |
tags |
tuple[str, ...]
|
Record-time tags, sorted and unique. (Curation tags, a deprecated field of the sidecar, are not indexed.) |
Source code in src/simscope/index.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
Importers¶
simscope.importers
¶
Importers for rollouts that already exist on disk.
Two sources are supported, both written through Library.record so the
content store deduplicates their meshes across runs (decision D15):
.rbundlefiles (artifacts-server / dial-mpc), via :func:import_rbundle;- self-contained Brax HTML viewers (
var system = "..."), via :func:import_brax_html.
:func:import_path sniffs a file (or walks a directory) and picks the
importer. Conventions of each source are documented in the section
comments below. Verified against the reference code and the real data:
.rbundle (core/viz3d/bundle.py)
[4B "RBDL"][u64 LE header length][JSON header][tail]; the tail is
gzip when header.compression == "gzip". buffers[k].off is a byte
offset into the uncompressed tail and count an element count.
body_pos is [T, B, 3] and body_quat [T, B, 4] in
wxyz (MuJoCo xquat), world frame, Z-up, metres. Body 0 is the
MuJoCo world body. Geoms carry MuJoCo geom_size conventions (the
same as MuJoCo: box half-extents, capsule/cylinder [r, h]
with the axis along local z, plane [hx, hy, spacing]) and
local_quat in wxyz. Mesh geoms hold the vertices already in the
geom's local frame (verts_count and faces_count count elements,
three per vertex or face) and their size is the mesh bounding box,
which is dropped. forces is [T, K, 2, 3] holding
[anchor, vector] in world frame (Newtons); inactive feet are NaN. It
is recorded as the contacts stream (viewer contracts 7), scaled so
one body weight draws as 1 m.
predictions is [T, H, L, 3]. The scene has no body hierarchy,
so every body gets parent = -1.
Brax HTML (brax/io/json.py, brax/visualizer/js/system.js)
The page holds var system = "<base64 of zlib(JSON)>" with keys
opt (opt.timestep is the playback frame interval, so
dt = opt.timestep), link_names, name, geoms and
states.x. Z-up, metres, same as MuJoCo. states.x[t] has
pos [L, 3] and rot [L, 4] (wxyz) for the L links, in
the world frame; the world body is not in the state, so our body 0 is a
fixed identity "world" and link i is body i + 1. geoms
maps a link name to its geoms; link_idx (-1 for the world, which
holds the floor and obstacles) gives the link, pos and rot
(wxyz) place the geom in the link frame. name is the type
(Plane, Sphere, Capsule, Cylinder, Box, Mesh;
HeightMap is not drawn by Brax and is skipped) and size is
MuJoCo's geom_size: box half-extents, sphere [r], capsule and
cylinder [r, half-length] with the axis along local z, plane
[hx, hy, spacing] where zero half-extents mean infinite. Meshes carry
vert [V, 3] and face [F, 3] in the geom's local frame
(size is then the bounding box, which is dropped). Numbers are
rounded to six decimals, so quaternions are renormalized on import.
ImportFormatError
¶
Bases: ValueError
Raised when an input file is not a supported rollout.
Source code in src/simscope/importers.py
87 88 | |
ImportResult
dataclass
¶
The outcome of importing one file.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
Path
|
The input file. |
name |
str | None
|
The new run, or |
n_frames |
int
|
Frames in the new run (0 on failure). |
error |
str | None
|
A one-line message if the import failed, else |
Source code in src/simscope/importers.py
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 | |
default_name(path: os.PathLike[str] | str) -> str
¶
Returns the unsanitized run name for a file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathLike[str] | str
|
An importable file. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The parent folder name for a file called |
str
|
the file stem. |
Source code in src/simscope/importers.py
122 123 124 125 126 127 128 129 130 131 132 133 | |
find_importable(paths: Sequence[os.PathLike[str] | str]) -> list[tuple[pathlib.Path, str]]
¶
Finds importable files in files and directories (recursively).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
Sequence[PathLike[str] | str]
|
Files or directories. Files are sniffed by content; in
directories only |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[Path, str]]
|
|
list[tuple[Path, str]]
|
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If a path does not exist. |
Source code in src/simscope/importers.py
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 | |
import_brax_html(lib: library.Library, path: os.PathLike[str] | str, *, name: str | None = None, tags: Sequence[str] = (), overwrite: bool = False) -> str
¶
Imports a self-contained Brax HTML viewer into a library.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib
|
Library
|
The target library. |
required |
path
|
PathLike[str] | str
|
The HTML file containing |
required |
name
|
str | None
|
Run name; default the file stem. Sanitized and de-duplicated
with a |
None
|
tags
|
Sequence[str]
|
Extra tags; |
()
|
overwrite
|
bool
|
Replace an existing run of the same name. |
False
|
Returns:
| Type | Description |
|---|---|
str
|
The name of the new run. |
Raises:
| Type | Description |
|---|---|
ImportFormatError
|
If the page has no valid Brax |
OSError
|
If the file cannot be read. |
Source code in src/simscope/importers.py
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 | |
import_files(lib: library.Library, files: Sequence[tuple[pathlib.Path, str]], *, tags: Sequence[str] = (), overwrite: bool = False, jobs: int | None = None) -> Iterator[ImportResult]
¶
Imports many files, in parallel processes, yielding in input order.
Run names are planned up front, so workers never race for a name. Writes into the shared content store are atomic, so concurrent workers are safe. A file that fails is reported in its result; the others go on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib
|
Library
|
The target library. |
required |
files
|
Sequence[tuple[Path, str]]
|
|
required |
tags
|
Sequence[str]
|
Extra tags for every run. |
()
|
overwrite
|
bool
|
Replace existing runs of the same names. |
False
|
jobs
|
int | None
|
Worker processes; |
None
|
Yields:
| Type | Description |
|---|---|
ImportResult
|
One result per file, in the order given. |
Source code in src/simscope/importers.py
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 | |
import_one(lib: library.Library, path: os.PathLike[str] | str, fmt: str | None = None, **kwargs: Any) -> str
¶
Imports one file with the importer that matches its content.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib
|
Library
|
The target library. |
required |
path
|
PathLike[str] | str
|
The file. |
required |
fmt
|
str | None
|
|
None
|
**kwargs
|
Any
|
|
{}
|
Returns:
| Type | Description |
|---|---|
str
|
The run name. |
Raises:
| Type | Description |
|---|---|
ImportFormatError
|
If the file is not a supported rollout. |
Source code in src/simscope/importers.py
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 | |
import_path(lib: library.Library, path: os.PathLike[str] | str, **kwargs: Any) -> list[str]
¶
Imports a file, or every importable file under a directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib
|
Library
|
The target library. |
required |
path
|
PathLike[str] | str
|
A |
required |
**kwargs
|
Any
|
|
{}
|
Returns:
| Type | Description |
|---|---|
list[str]
|
The names of the new runs, in path order. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
ImportFormatError
|
If |
Source code in src/simscope/importers.py
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 | |
import_rbundle(lib: library.Library, path: os.PathLike[str] | str, *, name: str | None = None, tags: Sequence[str] = (), overwrite: bool = False) -> str
¶
Imports an .rbundle into a library.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib
|
Library
|
The target library. |
required |
path
|
PathLike[str] | str
|
The |
required |
name
|
str | None
|
Run name. Default: the parent folder name for a file called
|
None
|
tags
|
Sequence[str]
|
Extra tags; |
()
|
overwrite
|
bool
|
Replace an existing run of the same name. |
False
|
Returns:
| Type | Description |
|---|---|
str
|
The name of the new run. |
Raises:
| Type | Description |
|---|---|
ImportFormatError
|
If the file is not a valid |
OSError
|
If the file cannot be read. |
Source code in src/simscope/importers.py
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | |
recorded_time(path: os.PathLike[str] | str) -> str
¶
Estimates when a source file was recorded, as a UTC timestamp.
A timestamp embedded in the file or its folder name wins (such as
20260911-132437_x.html or crate-20260910T005846Z), because
copying a file changes its modification time. Otherwise the file's
modification time is used.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathLike[str] | str
|
The source file. |
required |
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/simscope/importers.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |
unique_run_name(library_: library.Library, base: str, overwrite: bool = False, taken: Collection[str] = ()) -> str
¶
Builds a valid, unused run name from free text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_
|
Library
|
The target library. |
required |
base
|
str
|
Desired name; invalid characters become |
required |
overwrite
|
bool
|
Allow the name of a run already in the library. |
False
|
taken
|
Collection[str]
|
Names to avoid in any case, such as those planned for other files of the same batch. |
()
|
Returns:
| Type | Description |
|---|---|
str
|
The sanitized name, with |
Source code in src/simscope/importers.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
Simulator adapters¶
Both adapters record the contacts stream the viewer draws as force arrows:
add_contacts_stream declares it and contacts packs one frame.
simscope.mujoco
¶
Adapter for MuJoCo, MJX, and MuJoCo Warp.
scene_from_model turns a compiled MjModel into a
simscope.core.Scene. poses turns simulator state into the
[E, B, 7] pose array the recorder logs, converting MuJoCo's wxyz
quaternions to simscope's xyzw. Nothing here imports JAX or Warp: MJX and
Warp arrays only need to convert through np.asarray (or .numpy()).
contacts turns data.contact into the [1, K, 6] arrows frame of
the contacts stream (point and force on the robot, both in the world
frame), and add_contacts_stream declares that stream on a recorder with
the 1 / (m g) drawing scale.
Importing this module imports mujoco; import simscope does not.
Example
from simscope import mujoco as smj scene = smj.scene_from_model(model) frame = smj.poses(data) # [1, n_bodies, 7] smj.add_contacts_stream(rec, model, max_contacts=16) rec.log(frame, contacts=smj.contacts(model, data, max_contacts=16))
CONTACTS_STREAM = 'contacts'
module-attribute
¶
Name of the contacts stream (viewer contracts 7).
DEFAULT_MAX_CONTACTS = 16
module-attribute
¶
Contact slots per frame that add_contacts_stream reserves.
add_contacts_stream(recorder: Any, model: mujoco.MjModel, *, max_contacts: int = DEFAULT_MAX_CONTACTS, name: str = CONTACTS_STREAM) -> None
¶
Declares the contacts stream on a recorder.
Call it before the first log, then pass contacts(...) as the
contacts keyword of log.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recorder
|
Any
|
A |
required |
model
|
MjModel
|
The compiled model (its mass sets the drawing scale). |
required |
max_contacts
|
int
|
|
DEFAULT_MAX_CONTACTS
|
name
|
str
|
Stream name. |
CONTACTS_STREAM
|
Source code in src/simscope/mujoco.py
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 | |
contacts(model: mujoco.MjModel, data: mujoco.MjData, out: npt.NDArray[np.float32] | None = None, *, max_contacts: int | None = None) -> npt.NDArray[np.float32]
¶
Packs the active contacts of one MjData for the contacts stream.
Each row is [px, py, pz, fx, fy, fz]: the contact point (m) and the
force (N) that the world exerts on the robot, both in the world frame.
MuJoCo's contact force (mj_contactForce, rotated out of the contact
frame) is the force on geom 2, so the sign is flipped when the robot is
geom 1. The fixed side is the one whose geom belongs to the world body
(the ground, a wall); if neither geom does, the force on geom 2 is
used. Unused rows are zero, and so are contacts that carry no force. If
there are more than K force-carrying contacts, the K strongest
are kept.
For batched MJX or MuJoCo Warp data, convert each env to an MjData
(mjx.get_data(model, batch) returns one per env) and call this once
per env with out=frame[e], where frame is an [E, K, 6]
array. A fully batched path would need mj_contactForce on device
and is left out on purpose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
MjModel
|
The compiled model. |
required |
data
|
MjData
|
Simulator state; contacts are read as of the last step. |
required |
out
|
NDArray[float32] | None
|
Optional float32 C-contiguous buffer to fill (and return),
shape |
None
|
max_contacts
|
int | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A float32 array |
NDArray[float32]
|
shape, else a view of it), ready for |
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither |
Source code in src/simscope/mujoco.py
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | |
contacts_scale(model: mujoco.MjModel) -> float
¶
Metres of drawn arrow per newton: one body weight draws as 1 m.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
MjModel
|
The compiled model. |
required |
Returns:
| Type | Description |
|---|---|
float
|
|
float
|
magnitude, or |
float
|
|
Source code in src/simscope/mujoco.py
345 346 347 348 349 350 351 352 353 354 355 356 357 358 | |
poses(data_or_xpos: Any, xquat: Any | None = None, *, out: npt.NDArray[np.float32] | None = None) -> npt.NDArray[np.float32]
¶
Packs body poses into simscope's [E, B, 7] layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_or_xpos
|
Any
|
Either an object with |
required |
xquat
|
Any | None
|
Body quaternions in wxyz order, shape |
None
|
out
|
NDArray[float32] | None
|
Optional preallocated float32 C-contiguous |
None
|
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A float32 C-contiguous array |
NDArray[float32]
|
quaternion |
NDArray[float32]
|
leading axes of batched input are flattened into |
NDArray[float32]
|
never aliases the input. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If shapes are inconsistent, or |
TypeError
|
If |
Source code in src/simscope/mujoco.py
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | |
scene_from_model(model: mujoco.MjModel, *, collision: bool = True) -> core.Scene
¶
Builds a scene from a compiled MuJoCo model.
Bodies keep MuJoCo's ids, so the pose stream from poses lines up
with Scene.bodies. Geoms are attached to geom_bodyid with
geom_pos/geom_quat as their local pose. Mesh vertices are stored
exactly as mjModel holds them (already recentered and scaled by the
compiler), so the geom's local pose alone places them in the world and
Geom.scale is always 1.
Geom kinds: box, sphere, capsule, cylinder, ellipsoid, plane, and mesh
map one to one with MuJoCo's geom_size (unused entries zeroed).
Height fields are tessellated into meshes in the geom frame. Other
geom types (SDF) are skipped with a single warning.
Role rule (group only, as in mjviser, whose viewer shows groups 0-2 by
default): a geom is "collision" if its geom_group is 3 or
higher, or if its displayed alpha is 0; otherwise it is "visual".
contype/conaffinity are deliberately ignored, because in plain
MJCF a default (group 0) geom collides and is also the only thing to
look at.
Color rule (verified against mjv_updateScene): a geom with a
material takes mat_rgba unless its own rgba was set explicitly.
Materials are deduplicated. Only 2D textures are exported, as PNG;
cube maps and skyboxes are dropped. A mesh geom keeps its texture only
if the mesh has texture coordinates. Texture coordinates follow OpenGL
(v = 0 is the bottom of the image) and PNG rows are top-down.
MuJoCo's stored mesh normals are not exported; viewers compute them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
MjModel
|
A compiled |
required |
collision
|
bool
|
If false, keep only |
True
|
Returns:
| Type | Description |
|---|---|
Scene
|
The scene. Meshes, textures, and materials are deduplicated and |
Scene
|
include only what the kept geoms use. |
Source code in src/simscope/mujoco.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | |
source_info() -> dict[str, str]
¶
Describes the simulator for a rollout manifest.
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
|
dict[str, str]
|
MuJoCo Warp are reported as |
Source code in src/simscope/mujoco.py
63 64 65 66 67 68 69 70 | |
simscope.isaaclab
¶
Adapter for Isaac Lab 3.0+ (Isaac Sim 6.x).
scene_from_env reads the static scene from the live USD stage. poses
and PoseBuffer read asset.data.body_link_pose_w every step and give
the [E, B, 7] layout the recorder logs. Isaac Lab 3.0 already uses xyzw
quaternions and world-frame poses that include the env origins, so no
conversion is needed.
Nothing here imports torch, warp, isaaclab, omni, or isaacsim at import time.
Arrays are duck-typed (numpy, torch, Warp, or Isaac Lab's ProxyArray) and
torch or Warp are imported lazily inside the branch that needs them.
Importing this module does import pxr, which only exists after Isaac Sim
has started (for example after AppLauncher); import simscope does not.
contacts turns a ContactSensor's net body forces into the
[E, B, 6] arrows frame of the contacts stream, and
add_contacts_stream declares that stream with the 1 / (m g) scale.
Example
from simscope import isaaclab as sil scene = sil.scene_from_env(env, ["robot", "cube"]) frame = sil.poses(env, ["robot", "cube"]) # [E, B, 7] sil.add_contacts_stream(rec, n_bodies=len(sensor.body_names), mass=m) rec.log(frame, contacts=sil.contacts(sensor, robot))
CONTACTS_STREAM = 'contacts'
module-attribute
¶
Name of the contacts stream (viewer contracts 7).
PoseBuffer
¶
Stages poses in a device-side ring buffer, one host copy per block.
push copies this step's poses into a preallocated buffer on the same
device as the source arrays, so it never synchronizes with the host.
drain then moves everything to the host in a single transfer. For
numpy sources (CPU) it uses a preallocated numpy buffer instead.
Attributes:
| Name | Type | Description |
|---|---|---|
capacity |
Maximum number of frames held between drains. |
Source code in src/simscope/isaaclab.py
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
full: bool
property
¶
Whether the next push would overflow the buffer.
__init__(env: Any, assets: Sequence[str], capacity: int) -> None
¶
Binds the buffer to an env and a list of assets.
The device buffer is allocated lazily at the first push, with the
same framework and device as the first pose array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Any
|
An Isaac Lab environment (or a gym wrapper around one). |
required |
assets
|
Sequence[str]
|
Scene entity names, in pose order. |
required |
capacity
|
int
|
Frames to hold between drains. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/isaaclab.py
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | |
__len__() -> int
¶
Returns the number of frames waiting to be drained.
Source code in src/simscope/isaaclab.py
279 280 281 | |
drain() -> npt.NDArray[np.float32]
¶
Moves the buffered frames to the host and empties the buffer.
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A new float32 array |
NDArray[float32]
|
before the call. Uses exactly one device-to-host copy. |
Source code in src/simscope/isaaclab.py
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | |
push() -> None
¶
Copies the current poses into the ring buffer on the device.
Raises:
| Type | Description |
|---|---|
BufferError
|
If the buffer is full; call |
ValueError
|
If an asset's pose array changed shape. |
TypeError
|
If the assets mix torch and numpy arrays. |
Source code in src/simscope/isaaclab.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | |
add_contacts_stream(recorder: Any, n_bodies: int, *, mass: float, name: str = CONTACTS_STREAM) -> None
¶
Declares the contacts stream on a recorder.
Call it before the first log, then pass contacts(...) as the
contacts keyword of log.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recorder
|
Any
|
A |
required |
n_bodies
|
int
|
|
required |
mass
|
float
|
Robot mass in kg; one body weight ( |
required |
name
|
str
|
Stream name. |
CONTACTS_STREAM
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/isaaclab.py
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
contacts(sensor: Any, bodies: Any, *, out: npt.NDArray[np.float32] | None = None) -> npt.NDArray[np.float32]
¶
Packs a ContactSensor's net body forces for the contacts stream.
Row b of env e is [px, py, pz, fx, fy, fz]: the position of
sensor body b (where the arrow is drawn) and the net contact force
on it, both in the world frame (sensor.data.net_forces_w). Bodies
without contact give zero rows, which deflate to nothing. K is the
number of sensor bodies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sensor
|
Any
|
An Isaac Lab |
required |
bodies
|
Any
|
Where the sensor bodies are. Either the asset that owns
them (an articulation: its |
required |
out
|
NDArray[float32] | None
|
Optional float32 C-contiguous |
None
|
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A float32 array |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the shapes disagree, a sensor body is not found in
the asset, or |
Source code in src/simscope/isaaclab.py
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | |
env_origins(env: Any) -> npt.NDArray[np.float32]
¶
Reads the world origin of every env.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Any
|
An Isaac Lab environment (or a gym wrapper around one). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A float32 array |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/isaaclab.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
poses(env: Any, assets: Sequence[str]) -> npt.NDArray[np.float32]
¶
Reads body poses for the named assets as a host array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Any
|
An Isaac Lab environment (or a gym wrapper around one). |
required |
assets
|
Sequence[str]
|
Scene entity names, such as |
required |
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A new float32 C-contiguous array |
NDArray[float32]
|
quaternion |
NDArray[float32]
|
Bodies are ordered by asset, then by each asset's |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an asset's pose array has the wrong shape, or the assets disagree on the number of envs. |
Source code in src/simscope/isaaclab.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
scene_from_env(env: Any, assets: Sequence[str], *, collision: bool = True) -> core.Scene
¶
Builds a scene from the live USD stage of an Isaac Lab env.
For each asset, the body prims are those of env 0. An articulation's
bodies are its links, named by asset.body_names; a rigid object is its
root prim; a rigid object collection is one prim per object. Body paths
come from the physics view's link_paths when it offers them, and
otherwise from a search of the asset's root prim subtree. poses and
the returned scene use the same body order (asset by asset, then each
asset's own body_names order), so frame [e, b] is
Scene.bodies[b] in env e. Each body is named by its prim path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Any
|
An Isaac Lab environment (or a gym wrapper around one). |
required |
assets
|
Sequence[str]
|
Scene entity names, in pose order. |
required |
collision
|
bool
|
If false, keep only |
True
|
Returns:
| Type | Description |
|---|---|
Scene
|
The scene, extracted with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an asset's body prims cannot be located, or |
Source code in src/simscope/isaaclab.py
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 | |
scene_from_stage(stage: Any, body_paths: Sequence[str], *, collision: bool = True) -> core.Scene
¶
Builds a scene from bodies and geometry on a USD stage.
Bodies are the prims at body_paths, in that order, all with parent
-1: the runtime supplies a world pose per body per frame, so the tree
is not needed. Each body owns the geometry prims in its subtree, stopping
at any nested prim that is itself in body_paths. Instance proxies are
traversed.
Output is in metres: metersPerUnit is applied to sizes, vertices,
and offsets. A geom's pose is stored relative to its body prim (the USD
world transform with scale removed, which is what PhysX reports as the
link pose), so the stage's up axis cancels out and needs no rotation:
the pose stream fixes the world frame, and Isaac Lab stages are Z-up. A
Y-up stage logs a warning, because its world poses would have to be
rotated by the caller (+90 degrees about x) to be Z-up.
Geometry mapping:
Cubegivesbox;Spheregivessphere(ellipsoidunder non-uniform scale);Capsulegivescapsule(uniform scale only);Cylindergivescylinder(uniform radial scale).axisis honoured by rotating the shape onto local +z. Anything that a primitive cannot express (non-uniform capsule,Cone, taperedCylinder_1, sheared transforms) is tessellated into amesh.Meshgivesmeshwith polygons fan-triangulated. Non-uniform scale goes intoGeom.scale. Per-face materials (GeomSubset), normals, and UVs are not exported.Planeat least 100 m wide is an infiniteplane; smaller ones become a two-triangle mesh.- Other types (points, curves, point instancers) are skipped with one warning.
Role rule: a geom is "collision" if its computed purpose is
guide or proxy, or if it (or an ancestor up to the body) has
UsdPhysics.CollisionAPI and its purpose is not render. Everything
else is "visual". Refinement: if that would leave a body with no
visual geom, its default-purpose collision geoms are promoted to
"visual", because in Isaac Lab spawned primitives are usually both
the only shape and the collider. Invisible prims are skipped.
Material rule: see _read_material. Bound UsdPreviewSurface values
win, then MDL constants, then displayColor/displayOpacity. No
textures are exported.
Meshes and materials are deduplicated by content.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stage
|
Any
|
A |
required |
body_paths
|
Sequence[str]
|
Prim paths of the bodies, in pose order. |
required |
collision
|
bool
|
If false, keep only |
True
|
Returns:
| Type | Description |
|---|---|
Scene
|
The scene. Body names are the prim paths, and geom names are paths |
Scene
|
relative to their body. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/isaaclab.py
1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 | |
source_info() -> dict[str, str]
¶
Describes the simulator for a rollout manifest.
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
|
dict[str, str]
|
version is |
dict[str, str]
|
be found. |
Source code in src/simscope/isaaclab.py
61 62 63 64 65 66 67 68 69 70 71 72 73 | |
step_dt(env: Any) -> float
¶
Returns the env step interval, which is the recorded frame dt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
Any
|
An Isaac Lab environment (or a gym wrapper around one). |
required |
Returns:
| Type | Description |
|---|---|
float
|
|
Source code in src/simscope/isaaclab.py
126 127 128 129 130 131 132 133 134 135 | |
Curation and highlights¶
Marks, notes, ratings, labels, and groups (marks.group, and the ordered
list in .simscope/groups.json) are in simscope.annotations. The detectors
of simscope.highlights find peaks of the net contact force and of the
centre-of-mass acceleration. highlights.register adds your own kind of
marker. See Custom markers.
simscope.annotations
¶
Curation sidecars, the event-type registry and run groups (annotations v1).
runs/<name>/annotations.json holds marks, notes, ratings, events and
spatial annotations for one run. .simscope/event_types.json holds the
library's event vocabulary and .simscope/groups.json the ordered list of
run groups (a run belongs to at most one, marks.group). All are
pretty-printed with sorted keys and written atomically, so git can diff and
merge them (decision D8).
Every record carries a ULID id plus author, created and
updated. Lists are sorted by id when written, so two people's
appends merge cleanly; :func:merge is the programmatic equivalent.
MAX_GROUP_NAME = 64
module-attribute
¶
Longest group name, in characters.
Annotations
¶
The curation sidecar of one run, loaded into memory.
Mutators change the in-memory state and mark it dirty; :meth:save
writes it. author arguments default to the OS user name.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
Location of |
|
run_id |
The run's id from |
|
dt |
Seconds per frame, used to snap events to frames. |
|
n_frames |
Frames in the run, used to clip snapped frames. |
|
marks |
Run-level marks. |
|
notes |
list[Note]
|
Notes, sorted by id. |
ratings |
list[Rating]
|
Ratings, sorted by id. |
events |
list[Event]
|
Events, sorted by id. |
spatial |
list[Spatial]
|
Spatial annotations, sorted by id. |
extra |
dict[str, Any]
|
Unknown top-level fields read from disk. |
id_mismatch |
True if the file on disk belonged to another run id. |
Source code in src/simscope/annotations.py
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 | |
dirty: bool
property
¶
True if there are changes that save has not written.
__init__(path: os.PathLike[str] | str, run_id: str, dt: float, n_frames: int) -> None
¶
Creates an empty annotation set (nothing is read or written).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathLike[str] | str
|
Location of |
required |
run_id
|
str
|
The run's id. |
required |
dt
|
float
|
Seconds per frame (positive). |
required |
n_frames
|
int
|
Frames in the run. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/annotations.py
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 | |
add_event(type: str = '', *, t0: float, t1: float | None = None, label: str = '', env: int | None = None, props: dict[str, Any] | None = None, author: str | None = None) -> Event
¶
Adds an event, snapping f0 and f1 to the nearest frames.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
type
|
str
|
Key into the event-type registry, or |
''
|
t0
|
float
|
Start in seconds. |
required |
t1
|
float | None
|
End in seconds; defaults to |
None
|
label
|
str
|
Short display string. |
''
|
env
|
int | None
|
Env index, or |
None
|
props
|
dict[str, Any] | None
|
Typed property values. |
None
|
author
|
str | None
|
Defaults to the OS user name. |
None
|
Returns:
| Type | Description |
|---|---|
Event
|
The new event. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/annotations.py
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 | |
add_note(text: str, *, author: str | None = None) -> Note
¶
Adds a Markdown note.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The note body. |
required |
author
|
str | None
|
Defaults to the OS user name. |
None
|
Returns:
| Type | Description |
|---|---|
Note
|
The new note. |
Source code in src/simscope/annotations.py
797 798 799 800 801 802 803 804 805 806 807 | |
add_spatial(kind: str, position: tuple[float, float, float] | list[float], *, t: float, env: int = 0, frame: str = 'world', size: tuple[float, float, float] | list[float] | None = None, label: str = '', event: str | None = None, author: str | None = None) -> Spatial
¶
Adds a 3D point or box, snapping f to the nearest frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
|
required |
position
|
tuple[float, float, float] | list[float]
|
|
required |
t
|
float
|
Time in seconds. |
required |
env
|
int
|
Env index. |
0
|
frame
|
str
|
|
'world'
|
size
|
tuple[float, float, float] | list[float] | None
|
Half-extents of a box (required for |
None
|
label
|
str
|
Short display string. |
''
|
event
|
str | None
|
Optional id of a linked event. |
None
|
author
|
str | None
|
Defaults to the OS user name. |
None
|
Returns:
| Type | Description |
|---|---|
Spatial
|
The new annotation. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an argument is invalid. |
Source code in src/simscope/annotations.py
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 | |
get(record_id: str) -> Any
¶
Returns the record with this id.
Raises:
| Type | Description |
|---|---|
KeyError
|
If there is none. |
Source code in src/simscope/annotations.py
742 743 744 745 746 747 748 749 750 751 752 | |
load(run_dir: os.PathLike[str] | str, run_id: str, dt: float, n_frames: int) -> Self
classmethod
¶
Loads annotations.json from a run directory.
A missing file means "no annotations". If the file's run_id
differs from run_id a warning is logged and id_mismatch is
set; the records are kept and save will write the current id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_dir
|
PathLike[str] | str
|
The run directory. |
required |
run_id
|
str
|
The run's id. |
required |
dt
|
float
|
Seconds per frame. |
required |
n_frames
|
int
|
Frames in the run. |
required |
Returns:
| Type | Description |
|---|---|
Self
|
The annotations. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the file exists but is invalid. |
Source code in src/simscope/annotations.py
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 | |
rate(value: float, *, criterion: str = DEFAULT_CRITERION, scale: RatingScale = 'stars5', rationale: str | None = None, author: str | None = None) -> Rating
¶
Rates the run, replacing the author's rating for the criterion.
A replacement keeps the old id and created.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float
|
A number on |
required |
criterion
|
str
|
What is rated. |
DEFAULT_CRITERION
|
scale
|
RatingScale
|
The scale of |
'stars5'
|
rationale
|
str | None
|
Optional explanation. |
None
|
author
|
str | None
|
Defaults to the OS user name. |
None
|
Returns:
| Type | Description |
|---|---|
Rating
|
The new or updated rating. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the scale or value is invalid. |
Source code in src/simscope/annotations.py
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 | |
remove(record_id: str) -> bool
¶
Removes the record with this id.
Spatial annotations linked to a removed event keep their event
field; readers treat a dangling link as unlinked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record_id
|
str
|
A note, rating, event or spatial id. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if a record was removed. |
Source code in src/simscope/annotations.py
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 | |
save() -> bool
¶
Writes annotations.json atomically.
Nothing is written if there are no changes and no file exists yet, because writers create the file on the first change.
Returns:
| Type | Description |
|---|---|
bool
|
True if the file was written. |
Source code in src/simscope/annotations.py
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 | |
set_favorite(value: bool = True) -> None
¶
Sets or clears the favorite mark.
Source code in src/simscope/annotations.py
777 778 779 780 | |
set_group(group: str | None) -> None
¶
Moves the run into a group, or out of every group with None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
str | None
|
The group's name (see :func: |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the name is invalid. |
Source code in src/simscope/annotations.py
782 783 784 785 786 787 788 789 790 791 792 793 | |
snap(t: float) -> int
¶
Returns the frame nearest to t seconds, clipped to the run.
Uses floor(t / dt + 0.5) (round half up, as JavaScript's
Math.round) so the Python and player sides agree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
float
|
Time in seconds. |
required |
Returns:
| Type | Description |
|---|---|
int
|
A frame index in |
Source code in src/simscope/annotations.py
727 728 729 730 731 732 733 734 735 736 737 738 739 740 | |
to_json() -> dict[str, Any]
¶
Returns the file contents, lists sorted by id.
Source code in src/simscope/annotations.py
671 672 673 674 675 676 677 678 679 680 | |
update_event(event_id: str, **changes: Any) -> Event
¶
Changes fields of an event and re-snaps its frames.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_id
|
str
|
The event's id. |
required |
**changes
|
Any
|
Any of |
{}
|
Returns:
| Type | Description |
|---|---|
Event
|
The updated event. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If there is no event with this id. |
ValueError
|
If a field is unknown or the times are invalid. |
Source code in src/simscope/annotations.py
930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 | |
update_note(note_id: str, text: str) -> Note
¶
Replaces a note's text.
Raises:
| Type | Description |
|---|---|
KeyError
|
If there is no note with this id. |
Source code in src/simscope/annotations.py
809 810 811 812 813 814 815 816 817 818 819 820 | |
Event
dataclass
¶
Bases: Record
A time instant or segment on the timeline.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
str
|
Key into the event-type registry, or |
label |
str
|
Short display string. |
t0 |
float
|
Start in seconds. |
t1 |
float
|
End in seconds ( |
f0 |
int
|
Frame nearest |
f1 |
int
|
Frame nearest |
env |
int | None
|
Env index, or |
props |
dict[str, Any]
|
Typed property values. |
Source code in src/simscope/annotations.py
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | |
EventType
dataclass
¶
Bases: _JsonRecord
One entry of the event vocabulary.
Attributes:
| Name | Type | Description |
|---|---|---|
type_id |
str
|
The key the entry is stored under, such as |
name |
str
|
Display name. |
color |
str
|
|
key |
str | None
|
Optional single-character hotkey. |
props |
dict[str, dict[str, Any]]
|
Property specs by name; each has a |
extra |
dict[str, Any]
|
Unknown fields read from disk. |
Source code in src/simscope/annotations.py
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
__post_init__() -> None
¶
Validates color, hotkey and property specs.
Raises:
| Type | Description |
|---|---|
ValueError
|
If a field is invalid. |
Source code in src/simscope/annotations.py
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | |
from_json(obj: dict[str, Any], type_id: str = '') -> Self
classmethod
¶
Parses one entry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
dict[str, Any]
|
The entry's JSON object. |
required |
type_id
|
str
|
The key it was stored under. |
''
|
Raises:
| Type | Description |
|---|---|
FormatError
|
If the entry is invalid. |
Source code in src/simscope/annotations.py
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
to_json() -> dict[str, Any]
¶
Returns the JSON object (without type_id).
Source code in src/simscope/annotations.py
315 316 317 318 319 320 321 322 323 324 | |
Group
dataclass
¶
Bases: _JsonRecord
One entry of .simscope/groups.json.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The group's name, unique case-insensitively. |
created |
str
|
RFC 3339 UTC creation time. |
extra |
dict[str, Any]
|
Unknown fields read from disk. |
Source code in src/simscope/annotations.py
459 460 461 462 463 464 465 466 467 468 469 470 471 | |
Marks
dataclass
¶
Bases: _JsonRecord
Run-level marks. The last write wins.
flag, status and tags are deprecated since viewer v3.1
(decision D23): they are read and written back unchanged so that no data
is lost, but nothing sets them and the viewer does not show them.
Attributes:
| Name | Type | Description |
|---|---|---|
favorite |
bool
|
Whether the run is starred. |
group |
str | None
|
Name of the group the run belongs to, or |
flag |
str | None
|
Deprecated. Free-text flag such as |
status |
str | None
|
Deprecated. Free-text status, or |
tags |
list[str]
|
Deprecated. Curation tags, stored sorted and unique. |
extra |
dict[str, Any]
|
Unknown fields read from disk. |
Source code in src/simscope/annotations.py
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
__post_init__() -> None
¶
Normalizes tags to a sorted, unique list.
Source code in src/simscope/annotations.py
174 175 176 177 | |
Note
dataclass
¶
Bases: Record
A Markdown note.
Attributes:
| Name | Type | Description |
|---|---|---|
text |
str
|
The note body, Markdown. |
Source code in src/simscope/annotations.py
180 181 182 183 184 185 186 187 188 | |
Rating
dataclass
¶
Bases: Record
A rating on one criterion.
Attributes:
| Name | Type | Description |
|---|---|---|
criterion |
str
|
Free string, |
scale |
RatingScale
|
|
value |
float
|
Number on that scale. |
rationale |
str | None
|
Optional explanation. |
Source code in src/simscope/annotations.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | |
Record
dataclass
¶
Bases: _JsonRecord
Fields common to every list record.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
ULID, unique within the file. |
author |
str
|
Who made the record. |
created |
str
|
RFC 3339 UTC creation time. |
updated |
str
|
RFC 3339 UTC time of the last change. |
Source code in src/simscope/annotations.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
Spatial
dataclass
¶
Bases: Record
A 3D point or box at a moment in time.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
|
frame |
str
|
|
t |
float
|
Time in seconds. |
f |
int
|
Frame nearest |
env |
int
|
Env index. |
position |
list[float]
|
|
size |
list[float] | None
|
Box half-extents |
label |
str
|
Short display string. |
event |
str | None
|
Optional id of a linked event. |
Source code in src/simscope/annotations.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
check_group_name(name: object) -> str
¶
Validates a group name and returns it without surrounding spaces.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
object
|
The proposed name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The name, 1 to |
Raises:
| Type | Description |
|---|---|
ValueError
|
If it is not a string, is empty or too long, or has characters that cannot be printed (such as a newline). |
Source code in src/simscope/annotations.py
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 | |
default_author() -> str
¶
Returns the OS user name, which is the default record author.
Source code in src/simscope/annotations.py
52 53 54 55 56 57 | |
default_event_types() -> dict[str, EventType]
¶
Returns the built-in vocabulary used when the file is missing.
Source code in src/simscope/annotations.py
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | |
event_types_path(root: os.PathLike[str] | str) -> pathlib.Path
¶
Returns <root>/.simscope/event_types.json.
Source code in src/simscope/annotations.py
375 376 377 | |
groups_path(root: os.PathLike[str] | str) -> pathlib.Path
¶
Returns <root>/.simscope/groups.json.
Source code in src/simscope/annotations.py
474 475 476 | |
load_event_types(root: os.PathLike[str] | str) -> tuple[dict[str, EventType], dict[str, Any]]
¶
Loads the event vocabulary of a library.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
PathLike[str] | str
|
The library root. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, EventType]
|
|
dict[str, Any]
|
The built-in default (and no extra) when the file is missing. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the file is invalid. |
Source code in src/simscope/annotations.py
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 | |
load_groups(root: os.PathLike[str] | str) -> tuple[list[Group], dict[str, Any]]
¶
Loads the ordered groups of a library.
A group whose name repeats an earlier one (ignoring case) is dropped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
PathLike[str] | str
|
The library root. |
required |
Returns:
| Type | Description |
|---|---|
list[Group]
|
|
dict[str, Any]
|
top-level fields. No groups when the file is missing. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the file is invalid. |
Source code in src/simscope/annotations.py
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | |
merge(a: Annotations, b: Annotations, base: Annotations | None = None) -> Annotations
¶
Merges two annotation sets that diverged from a common base (D8).
List entries merge by id: the union of both sides, and where the same id
exists on both sides the one with the later updated wins. Ratings are
then reduced to one per author and criterion. This is what a textual git
merge of the two sorted files would do, without conflicts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Annotations
|
One side. The result takes its path, run id and dt. |
required |
b
|
Annotations
|
The other side ("theirs"; wins mark conflicts). |
required |
base
|
Annotations | None
|
The common ancestor, if known. It lets the merge propagate deletions and tell which side changed a mark. |
None
|
Returns:
| Type | Description |
|---|---|
Annotations
|
A new, dirty |
Source code in src/simscope/annotations.py
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 | |
save_event_types(root: os.PathLike[str] | str, types: dict[str, EventType], extra: dict[str, Any] | None = None) -> pathlib.Path
¶
Writes the event vocabulary atomically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
PathLike[str] | str
|
The library root. |
required |
types
|
dict[str, EventType]
|
Entries by id. |
required |
extra
|
dict[str, Any] | None
|
Unknown top-level fields to keep. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
The path written. |
Source code in src/simscope/annotations.py
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
save_groups(root: os.PathLike[str] | str, groups: list[Group], extra: dict[str, Any] | None = None) -> pathlib.Path
¶
Writes the ordered groups atomically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
PathLike[str] | str
|
The library root. |
required |
groups
|
list[Group]
|
The groups in library order. |
required |
extra
|
dict[str, Any] | None
|
Unknown top-level fields to keep. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
The path written. |
Source code in src/simscope/annotations.py
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | |
utc_now() -> str
¶
Returns the current UTC time as RFC 3339 with seconds precision.
Source code in src/simscope/annotations.py
542 543 544 | |
simscope.highlights
¶
Automatic highlights: the moments where a rollout's physics peaks.
A highlight is a marker on the timeline. Two kinds are built in, both computed from signals every simulator provides, so they mean the same for any robot or task:
contact
The net contact force: the norm of the sum of all the force vectors
of the contacts stream. A rollout
without a contacts stream gets none.
acceleration
The centre-of-mass acceleration: the second difference of the centre
of mass, in m/s^2. The centre of mass is the mean of the body positions
weighted by Body.mass; a scene without masses uses the plain mean of
the bodies other than the world. A robot standing still reads about 0,
free fall reads about 9.8, and an impact or a push-off is a peak.
A signal is scored per env by a robust z-score (x - median) / max(1.4826 *
MAD, floor). The floor keeps flat and intermittent signals from exploding
(see SPREAD_FLOOR). A highlight is a local maximum within +-0.25 s whose
score is above 6. The top 10 per env and kind are kept, and the top 50 per
kind across envs; markers of one kind within 0.15 s merge. ratio says
how many times the run's typical peak the value is.
Nothing else is built in: there are no task words such as "jump" or "fall"
here. To mark something that is specific to your robot, register your own
kind with :func:register, or add explicit events with
Annotations.add_event (see "Custom markers" in the getting-started
guide).
Everything is vectorized over [T, E] and reads the run one window at a
time, decoding env chunks on threads, so 1,000 frames of 4,096 envs take a
few seconds and a single 400-frame run takes milliseconds. Teleports (env
resets) are masked out.
Example
from simscope import highlights doc = highlights.load_or_compute(rollout, cache_dir) [(h["t"], h["kind"]) for h in doc["highlights"]]
ACCEL_FLOOR = 0.5
module-attribute
¶
Scale floor of the acceleration, in m/s^2 (q16d noise is about 0.04).
COLOR_PATTERN = re.compile('^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$')
module-attribute
¶
What a custom kind's colour looks like: #rgb or #rrggbb.
CONTACT_FLOOR = 1.0
module-attribute
¶
Scale floor of the net contact force, in newtons.
DETECTOR_VERSION = 'simscope/3'
module-attribute
¶
Version of the built-in detectors; a change invalidates cached results.
Detector = Callable[[library.Rollout], list[Highlight]]
module-attribute
¶
A custom detector: returns highlights whose kind is its key.
GRAVITY = 9.81
module-attribute
¶
Standard gravity in m/s^2, for the g of a detail.
KEY_PATTERN = re.compile('^[a-z][a-z0-9_]{0,31}$')
module-attribute
¶
What a custom kind's key looks like: lower case, digits and _.
MAD_SIGMA = 1.4826
module-attribute
¶
Scales the median absolute deviation to a standard deviation.
MAX_LABEL = 40
module-attribute
¶
Longest label of a custom kind, in characters.
MERGE_S = 0.15
module-attribute
¶
Markers of one kind closer than this merge into one.
MIN_FRAMES = 8
module-attribute
¶
Fewest frames a signal needs for a typical level to mean anything.
PER_ENV = 10
module-attribute
¶
Most highlights per env and kind.
PER_KIND = 50
module-attribute
¶
Most highlights per kind across envs.
REL_FLOOR = 0.05
module-attribute
¶
Scale floor as a fraction of the median, so a steady load is not flat.
Reducer = Callable[[npt.NDArray[np.float32], int], npt.NDArray[np.float32]]
module-attribute
¶
Maps a window [t, e, c] of envs starting at lo to [t, e, ...].
SPREAD_FLOOR = 0.25
module-attribute
¶
Scale floor as a fraction of the signal's usual peak excursion.
A gait's contact force is zero half the time, so its MAD is near zero and
every stride would score as an outlier. The usual peak is the k-th
largest value, k = max(TOP_MIN, TOP_FRACTION * T); a highlight has to
beat it by 50 %.
TELEPORT_SPEED = 20.0
module-attribute
¶
A centre-of-mass step faster than this (m/s) is a reset, not motion.
WINDOW_S = 0.25
module-attribute
¶
A highlight is the largest score within this many seconds.
Z_THRESHOLD = 6.0
module-attribute
¶
Least robust z-score of a highlight.
Highlight
dataclass
¶
One marker on the timeline (viewer contracts 9.3).
Attributes:
| Name | Type | Description |
|---|---|---|
t |
float
|
Time in seconds (the start, for a span). |
frame |
int
|
Frame index. |
env |
int
|
Env index. |
kind |
str
|
Key of the kind of marker, such as |
score |
float
|
Number that ranks the marker within its kind (higher is more notable). The built-in kinds use the robust z-score. |
value |
float
|
The headline measurement, in the unit of the kind: newtons of
|
body |
int | None
|
Body index the marker belongs to, or |
label |
str
|
Name shown for this marker; the kind's label if left empty. |
detail |
str
|
One short phrase with the numbers behind the marker, with
units, such as |
t1 |
float | None
|
End of a span in seconds, or |
frame1 |
int | None
|
End frame of a span, or |
ratio |
float | None
|
How many times the typical level the value is, or |
also |
tuple[str, ...]
|
Keys of other kinds that were merged into this marker. |
Source code in src/simscope/highlights.py
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
cache_key(rollout: library.Rollout) -> str
¶
Identifies the inputs of a cached result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A string of run id, detector version, the registered custom kinds |
str
|
(key, label and colour) and the manifest's modification time. |
Source code in src/simscope/highlights.py
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 | |
detect(rollout: library.Rollout, *, envs: Sequence[int] | None = None) -> list[Highlight]
¶
Runs every registered detector on a finished run.
A custom detector that raises, or whose result is invalid (see
:func:register), is logged and skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run. It must not be recording. |
required |
envs
|
Sequence[int] | None
|
Only look at these envs (built-in detectors read only them;
custom detectors see the whole run and their result is
filtered). |
None
|
Returns:
| Type | Description |
|---|---|
list[Highlight]
|
The highlights, sorted by time (then env and kind). |
Raises:
| Type | Description |
|---|---|
IndexError
|
If an env index is out of range. |
FormatError
|
If the run is unfinished. |
Source code in src/simscope/highlights.py
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 | |
kinds() -> list[dict[str, str]]
¶
Lists the registered kinds as {key, label} objects.
A custom kind that has a colour also carries color.
Source code in src/simscope/highlights.py
240 241 242 243 244 245 246 247 248 249 250 251 | |
load_or_compute(rollout: library.Rollout, cache_dir: os.PathLike[str] | str) -> dict[str, Any]
¶
Returns the highlights document, cached in cache_dir.
The cache is highlights.json plus a highlights.key file beside
it; both are replaced atomically, the key last. A different run id,
detector version, set of registered custom kinds or manifest mtime makes
the next call recompute.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
A finished run. |
required |
cache_dir
|
PathLike[str] | str
|
Directory for the cache (created if missing), for example
|
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The |
Source code in src/simscope/highlights.py
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 | |
register(key: str, detector: Detector, *, label: str, color: str | None = None) -> None
¶
Adds your own kind of marker to the ones :func:detect runs.
The detector runs once per run, in the background, and its markers are cached with the built-in ones. Adding or changing a detector refreshes the cache. A detector that raises, or returns something invalid, is logged and skipped; the other kinds still work.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Name of the kind: a lower case letter, then lower case letters,
digits and |
required |
detector
|
Detector
|
|
required |
label
|
str
|
Display name of the kind (at most 40 characters), used for highlights that have no label of their own. |
required |
color
|
str | None
|
A CSS hex colour for the marker, such as |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the key, label or colour is invalid, or the key is already registered. |
Source code in src/simscope/highlights.py
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | |
root_body(names: Sequence[str] | core.Scene) -> int
¶
Picks the followed (root) body of a scene.
That is the first of :data:ROOT_NAMES (exact names first, then
prefixes), else the first body that is not the world. The derived
root-pose stream and the player's follow camera use this same rule
(FOLLOW_ALIASES in web/src/core/player.js).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
names
|
Sequence[str] | Scene
|
Body names in scene order, or the scene itself. |
required |
Returns:
| Type | Description |
|---|---|
int
|
A body index. |
Source code in src/simscope/highlights.py
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
to_json(rollout: library.Rollout, highlights: Sequence[Highlight]) -> dict[str, Any]
¶
Builds the highlights.json object (viewer contracts 9.3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run the highlights belong to. |
required |
highlights
|
Sequence[Highlight]
|
Highlights from :func: |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The JSON-ready object. |
dict[str, Any]
|
one highlight (as its kind or among its |
dict[str, Any]
|
order; a custom kind with a colour carries it. |
Source code in src/simscope/highlights.py
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 | |
unregister(key: str) -> None
¶
Removes a kind, built-in or registered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Kind key. |
required |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no such kind is registered. |
Source code in src/simscope/highlights.py
228 229 230 231 232 233 234 235 236 237 | |
Derived data¶
simscope.derived
¶
Derived data: files computed once per run and cached (contracts 5).
Everything here is "just more files in the existing formats", so the browser
reads it through the same path-addressed Source as the recording itself:
root_pose.blk: the followed body of every env,[E, 1, 7]q16dblocks in the normal block format, for runs with more than :data:CROWD_ENVSenvs. It is what the crowd tier draws.summaries.json: one number per env for each of a few columns, which sorts the env picker.envelopes/<stream>.json: the p5, p50 and p95 of a scalar or vector stream across envs, per frame.highlights.json: the contact-force and acceleration peaks of :mod:simscope.highlights;n_highlightscounts them per env.
Files live in .simscope/derived/<run id>/ next to a stamp.json that
holds the digest of the run's manifest: when the manifest changes, every
derived file of the run is discarded. Nothing is computed for a run that is
still recording.
Every pass reads one window at a time, and only the components it needs: a block stores its components as separate byte planes, so the root body is inflated from a 140-float item and unshuffled as 7 floats. A 4,096-env, 1,000-frame, 20-body run never holds more than one window of one body.
CROWD_ENVS = 64
module-attribute
¶
Runs with more envs than this get a root_pose.blk.
VERSION = 4
module-attribute
¶
Bump to discard every cached derived file.
2: highlights are simscope-highlights/2 (viewer v3.1).
3: detector simscope/2.1, a fall is a body that stayed down.
4: detector simscope/3, only contact and acceleration kinds.
applicable(rollout: library.Rollout, what: str) -> bool
¶
Tells whether a derived file exists for this run at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run. |
required |
what
|
str
|
A path relative to |
required |
Returns:
| Type | Description |
|---|---|
bool
|
False for a run that is still recording, for a root stream when |
bool
|
there are at most :data: |
bool
|
stream that is not scalar or vector (or of a single env), for |
bool
|
highlights when :mod: |
bool
|
anything else that is not derived data. |
Source code in src/simscope/derived.py
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
cache_dir(lib_root: pathlib.Path, rollout: library.Rollout) -> pathlib.Path
¶
Returns the cache folder of one run, keyed by the run's ULID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib_root
|
Path
|
The library folder. |
required |
rollout
|
Rollout
|
The run. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
|
Source code in src/simscope/derived.py
98 99 100 101 102 103 104 105 106 107 108 | |
cache_root(lib_root: pathlib.Path) -> pathlib.Path
¶
Returns the folder that holds every run's derived cache.
That is <library>/.simscope/derived, or a per-library folder under
the system temp directory when the library is read-only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib_root
|
Path
|
The library folder. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The cache folder (not necessarily created yet). |
Source code in src/simscope/derived.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
carry_over(lib_root: pathlib.Path, rollout: library.Rollout, before: tuple[str, str]) -> None
¶
Keeps a run's cache valid after its manifest was rewritten.
A rename rewrites rollout.json (only its name changes), which
would make the stamp and the highlights key look stale and throw the
cache away. If the cache was valid for before (:func:fingerprint
taken before the rewrite), it is stamped for the run as it is now.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lib_root
|
Path
|
The library folder. |
required |
rollout
|
Rollout
|
The run, opened after the rewrite. |
required |
before
|
tuple[str, str]
|
The fingerprint from before the rewrite. |
required |
Source code in src/simscope/derived.py
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
ensure(rollout: library.Rollout, cache: pathlib.Path, what: str) -> pathlib.Path | None
¶
Computes a derived file if it is missing or stale, and returns it.
Blocks until the file exists; the server runs it in a worker thread. Two callers asking for the same run wait for each other, so a pass runs once. The file is written atomically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run (complete). |
required |
cache
|
Path
|
The run's cache folder, from :func: |
required |
what
|
str
|
See :func: |
required |
Returns:
| Type | Description |
|---|---|
Path | None
|
The file path, or |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the run's data is corrupt. |
OSError
|
If the cache cannot be written. |
Source code in src/simscope/derived.py
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | |
envelope_name(stream: str) -> str
¶
Returns the derived file name of a stream's envelope.
Source code in src/simscope/derived.py
211 212 213 | |
fingerprint(rollout: library.Rollout) -> tuple[str, str]
¶
Returns what the cache of a run is valid for, to carry it over.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run. |
required |
Returns:
| Type | Description |
|---|---|
tuple[str, str]
|
The manifest digest and the highlights cache key. |
Source code in src/simscope/derived.py
144 145 146 147 148 149 150 151 152 153 | |
fresh(rollout: library.Rollout, cache: pathlib.Path, what: str) -> pathlib.Path | None
¶
Returns the cached file if it is up to date, without computing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run. |
required |
cache
|
Path
|
The run's cache folder. |
required |
what
|
str
|
See :func: |
required |
Returns:
| Type | Description |
|---|---|
Path | None
|
The file path, or |
Source code in src/simscope/derived.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | |
highlight_count(cache: pathlib.Path) -> int | None
¶
Counts the highlights in a run's cached file, if it has one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache
|
Path
|
The run's cache folder. |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
The count, or |
Source code in src/simscope/derived.py
270 271 272 273 274 275 276 277 278 279 280 281 282 283 | |
manifest_digest(rollout: library.Rollout) -> str
¶
Hashes the run's manifest file, which is what invalidates the cache.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rollout
|
Rollout
|
The run. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A hex digest, or |
Source code in src/simscope/derived.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
root_body(scene: core.Scene) -> int
¶
Picks the followed body (see :func:simscope.highlights.root_body).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scene
|
Scene
|
The run's scene. |
required |
Returns:
| Type | Description |
|---|---|
int
|
A body index (never the world body when another exists). |
Source code in src/simscope/derived.py
465 466 467 468 469 470 471 472 473 474 | |
Viewer server¶
simscope serve starts this app. It needs the viewer extra.
simscope.server
¶
The simscope viewer server: Starlette over a library directory.
create_app(root, author=...) builds the ASGI app and serve runs it
with uvicorn. The routes read the library folder on disk.
create_app(root: pathlib.Path | str, *, author: str | None = None, token: str | None = None, allowed_hosts: Sequence[str] | None = None, watch: bool = True, warm: bool = True) -> Starlette
¶
Builds the app that serves one library to the browser.
The library is scanned before this returns. The file watcher and the
highlight warmer start with the app's lifespan (uvicorn runs it; a
test client runs it inside with).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path | str
|
The library folder. |
required |
author
|
str | None
|
Author of annotations made through the API; the OS user name
if |
None
|
token
|
str | None
|
The token that writes must send in |
None
|
allowed_hosts
|
Sequence[str] | None
|
|
None
|
watch
|
bool
|
Watch the folder for changes. Turn off to drive
|
True
|
warm
|
bool
|
Compute highlights of every run in the background. |
True
|
Returns:
| Type | Description |
|---|---|
Starlette
|
The ASGI app. Its services are on |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
Source code in src/simscope/server/app.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
serve(root: pathlib.Path | str, host: str = '127.0.0.1', port: int = 8080, author: str | None = None, *, token: str | None = None) -> None
¶
Serves a library until interrupted.
Binds host (loopback by default). A loopback bind answers only
loopback Host headers, which stops DNS rebinding; a wildcard bind
answers any.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path | str
|
The library folder. |
required |
host
|
str
|
Address to listen on. |
'127.0.0.1'
|
port
|
int
|
Port to listen on (0 picks a free one). |
8080
|
author
|
str | None
|
Author of annotations made through the API. |
None
|
token
|
str | None
|
The write token; random if |
None
|
Source code in src/simscope/server/app.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |
Export¶
export_html writes one offline HTML file. For layout="compare" the
arrange keyword sets how the panes sit: "side" (side by side, the default
for one or two rollouts), "stack" (one above the other), or "grid" (two
by two, the default for three or four). Lean files carry no highlights.
ui="full" files do.
simscope.export
¶
Exports runs as one offline HTML file or as a .simscope pack.
The HTML file has no external dependencies. It carries:
- the player runtime (
simscope-player.js), gzipped and base64-encoded in a<script type="text/plain">block, plus a tiny classic bootstrap script that decodes it (Uint8Array.fromBase64with anatobfallback), gunzips it withDecompressionStreamand starts it from ablob:script; - one pack holding every run (shared scenes and meshes are stored once),
base64-encoded in a second
<script type="text/plain">block that all players reference withsrc="#simscope-pack"; - the full third-party license text, in an HTML comment at the end.
Given the same inputs, the output is byte-identical: entries are sorted and
gzip uses a fixed level and mtime=0. No timestamps are written.
Annotation sidecars (annotations.json) are copied verbatim into the pack,
so notes, ratings and author names travel with it. Pass annotations=False
to leave them out.
ui="full" swaps the lean <simscope-player> page for the whole app
(simscope-app.js and .css) started from a boot block that points at the
same inline pack (viewer contracts 6). The pack also carries each
run's derived data under derived/<run>/: the highlights (full exports
only, because the lean player does not draw them) and, for more than 64 envs,
the crowd root-pose stream and per-env summaries. envs=[...] exports only
those envs.
The lean page is deliberately bare (viewer contracts 10): a dark/light
neutral background and the <simscope-player> elements, nothing else. The
runtime lays out a compare page itself, in the arrangement named by
data-arrange, and adds the one shared control bar.
ARRANGEMENTS = ('side', 'stack', 'grid')
module-attribute
¶
How a compare page lays out its panes: side by side (a horizontal split), stacked (a vertical split), or a 2 x 2 grid (one cell empty for three runs).
COMPARE_MAX = 4
module-attribute
¶
A compare page takes up to this many runs.
COMPARE_SYNC = 'compare'
module-attribute
¶
Name of the shared clock of a compare page.
CROWD_ENVS = 64
module-attribute
¶
Runs exported with more envs than this carry the crowd-tier files.
build_html(pack_bytes: bytes, runs: Sequence[str], *, title: str | None = None, layout: Layout = 'grid', arrange: Arrange | None = None, autoplay: bool = True, loop: bool = True, ui: Ui = 'lean') -> bytes
¶
Builds the HTML page around an existing pack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pack_bytes
|
bytes
|
A pack holding at least the named runs. |
required |
runs
|
Sequence[str]
|
Runs to show, in page order. |
required |
title
|
str | None
|
Page title; defaults to the run name for a single run and to
|
None
|
layout
|
Layout
|
|
'grid'
|
arrange
|
Arrange | None
|
For |
None
|
autoplay
|
bool
|
Whether playback starts once the players are ready
( |
True
|
loop
|
bool
|
Whether playback restarts at the end ( |
True
|
ui
|
Ui
|
|
'lean'
|
Returns:
| Type | Description |
|---|---|
bytes
|
The UTF-8 page. |
Raises:
| Type | Description |
|---|---|
ValueError
|
On an unknown layout, |
Source code in src/simscope/export.py
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 | |
build_pack(library_root: os.PathLike[str] | str, runs: Sequence[str], *, transcode: bool = True, annotations: bool = True, envs: Sequence[int] | None = None, derived: bool = True, with_highlights: bool = True) -> bytes
¶
Builds one pack holding all runs.
All runs go through :func:simscope.io.pack.write_pack together, so
shared scenes and meshes are stored once. Each run's poster.png is
included when it exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_root
|
PathLike[str] | str
|
The library directory, or a folder that contains libraries. |
required |
runs
|
Sequence[str]
|
Names of the runs to include. |
required |
transcode
|
bool
|
Whether to shrink the pack (q16d poses, q16 meshes). |
True
|
annotations
|
bool
|
Whether to keep each run's |
True
|
envs
|
Sequence[int] | None
|
Keep only these envs of every run (see
:func: |
None
|
derived
|
bool
|
Whether to add the |
True
|
with_highlights
|
bool
|
Whether those entries include |
True
|
Returns:
| Type | Description |
|---|---|
bytes
|
The pack bytes. |
Raises:
| Type | Description |
|---|---|
ValueError
|
On an empty or duplicate run list, an invalid run name,
a run that is still recording, or an invalid |
FileNotFoundError
|
If a run or a file it references is missing. |
Source code in src/simscope/export.py
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | |
derived_entries(library_root: os.PathLike[str] | str, runs: Sequence[str], scratch: pathlib.Path, *, envs: Sequence[int] | None = None, with_highlights: bool = True) -> dict[str, pack.Source]
¶
Collects the derived/<run>/ pack entries of runs.
Highlights are included for every run whose highlights can be computed,
unless with_highlights is false.
A run that still has more than 64 envs after envs also gets its
crowd-tier root_pose.blk and summaries.json, when
:mod:simscope.derived is present. Nothing is computed for a run that
is still recording, and a derived file that fails to compute is left out
with a warning: derived data is optional in a pack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_root
|
PathLike[str] | str
|
The library directory, or a folder that contains libraries. |
required |
runs
|
Sequence[str]
|
Run names (validated by the caller). |
required |
scratch
|
Path
|
A folder for files made on the way; it must outlive the pack write. |
required |
envs
|
Sequence[int] | None
|
The env subset of the export, if any. |
None
|
with_highlights
|
bool
|
Whether to include |
True
|
Returns:
| Type | Description |
|---|---|
dict[str, Source]
|
Entries by pack path, ready for |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/export.py
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | |
export_html(library_root: os.PathLike[str] | str, runs: Sequence[str], out_path: os.PathLike[str] | str, *, title: str | None = None, layout: Layout = 'grid', arrange: Arrange | None = None, transcode: bool = True, annotations: bool = True, autoplay: bool = True, loop: bool = True, ui: Ui = 'lean', envs: Sequence[int] | None = None) -> pathlib.Path
¶
Exports runs as one self-contained HTML file.
The file makes no network requests and works from file://. With
ui="lean" runs appear as <simscope-player> elements in the order
given; with ui="full" the page is the whole app, started on the same
pack. One pack holds all runs, so scenes and meshes shared between runs
are stored once. A full export's pack also carries each run's
highlights; for more than 64 envs, either kind carries the crowd root-pose
stream and summaries. Output is deterministic for equal inputs.
A lean page is bare: the players fill the window and the runtime adds one
control bar (viewer contracts 10). A compare page looks like the
app's compare view: the panes fill the page, each with its run name as a
title and nothing else, and one control bar below them drives every pane.
A pack is inlined as base64 in a string, which Chrome caps at 512 MiB, so
very large runs need envs to pick a subset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_root
|
PathLike[str] | str
|
The library directory, or a folder that contains libraries. |
required |
runs
|
Sequence[str]
|
Names of the runs to export. |
required |
out_path
|
PathLike[str] | str
|
The HTML file to write (replaced atomically). |
required |
title
|
str | None
|
Page title; defaults to the run name for a single run and to
|
None
|
layout
|
Layout
|
|
'grid'
|
arrange
|
Arrange | None
|
For |
None
|
transcode
|
bool
|
Whether to shrink the pack (q16d poses, q16 meshes). |
True
|
annotations
|
bool
|
Whether to include each run's |
True
|
autoplay
|
bool
|
Whether playback starts once the players are ready
( |
True
|
loop
|
bool
|
Whether playback restarts at the end ( |
True
|
ui
|
Ui
|
|
'lean'
|
envs
|
Sequence[int] | None
|
Export only these envs of every run, in this order; they are
renumbered from 0. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
The output path. |
Raises:
| Type | Description |
|---|---|
ValueError
|
On an unknown layout, |
FileNotFoundError
|
If a run or a file it references is missing. |
Source code in src/simscope/export.py
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 | |
export_pack(library_root: os.PathLike[str] | str, runs: Sequence[str], out_path: os.PathLike[str] | str, *, transcode: bool = True, annotations: bool = True, envs: Sequence[int] | None = None, derived: bool = True) -> pathlib.Path
¶
Writes runs to a .simscope pack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_root
|
PathLike[str] | str
|
The library directory, or a folder that contains libraries. |
required |
runs
|
Sequence[str]
|
Names of the runs to include. |
required |
out_path
|
PathLike[str] | str
|
The pack file to write (replaced atomically). |
required |
transcode
|
bool
|
Whether to shrink the pack (q16d poses, q16 meshes). |
True
|
annotations
|
bool
|
Whether to keep each run's |
True
|
envs
|
Sequence[int] | None
|
Keep only these envs of every run. |
None
|
derived
|
bool
|
Whether to add highlights and crowd data (see
:func: |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
The output path. |
Raises:
| Type | Description |
|---|---|
ValueError
|
On an empty or duplicate run list, an invalid run name,
a run that is still recording, or an invalid |
FileNotFoundError
|
If a run or a file it references is missing. |
Source code in src/simscope/export.py
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
gzip_runtime(runtime: bytes) -> bytes
¶
Compresses the player runtime deterministically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime
|
bytes
|
The player IIFE. |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
A gzip stream (fixed level, |
Source code in src/simscope/export.py
439 440 441 442 443 444 445 446 447 448 | |
read_asset(name: str) -> bytes
¶
Returns the bytes of a file shipped in simscope/_assets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
File name, for example |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
The file contents. |
Source code in src/simscope/export.py
81 82 83 84 85 86 87 88 89 90 91 | |
Data model¶
simscope.core
¶
Simulator-neutral data model for scenes and rollouts.
These types are the in-memory contract shared by the adapters, the storage
layer, the viewer, and the exporter. The on-disk encoding is simscope.io.
Conventions: Z-up, right-handed, metres, seconds. A pose is
[px, py, pz, qx, qy, qz, qw] with the quaternion in xyzw order.
POSE_DIM = 7
module-attribute
¶
Floats per pose: position xyz, then quaternion xyzw.
Body
dataclass
¶
A rigid body that owns geoms and has one pose per frame.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Body name or USD path. Not required to be unique. |
parent |
int
|
Index of the parent body, or |
mass |
float
|
Mass in kilograms, or |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/core.py
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |
__post_init__() -> None
¶
Checks the mass.
Source code in src/simscope/core.py
146 147 148 149 | |
Geom
dataclass
¶
A visual or collision shape attached to a body.
kind and size follow MuJoCo conventions.
Attributes:
| Name | Type | Description |
|---|---|---|
body |
int
|
Index into |
kind |
GeomKind
|
Shape kind. |
size |
Vec3
|
Kind-specific size parameters in metres. |
pos |
Vec3
|
Position in the body frame. |
quat |
Quat
|
Orientation in the body frame, xyzw. |
scale |
Vec3
|
Component-wise scale applied to local vertices. |
material |
int
|
Index into |
mesh |
int | None
|
Index into |
role |
GeomRole
|
Whether viewers show the geom by default. |
name |
str
|
Optional geom name. |
Source code in src/simscope/core.py
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
Material
dataclass
¶
Surface appearance of a geom.
Attributes:
| Name | Type | Description |
|---|---|---|
rgba |
Rgba
|
Base color and opacity, each in |
metallic |
float
|
PBR metalness in |
roughness |
float
|
PBR roughness in |
texture |
int | None
|
Index into |
texrepeat |
tuple[float, float]
|
Texture repeats along u and v. |
Source code in src/simscope/core.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
Mesh
dataclass
¶
A triangle mesh in its geom's local frame.
Attributes:
| Name | Type | Description |
|---|---|---|
vertices |
NDArray[float32]
|
Vertex positions, shape |
faces |
NDArray[uint32]
|
Triangle vertex indices, shape |
normals |
NDArray[float32] | None
|
Optional per-vertex normals, shape |
uvs |
NDArray[float32] | None
|
Optional per-vertex texture coordinates, shape |
Source code in src/simscope/core.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
__post_init__() -> None
¶
Validates array shapes and dtypes.
Raises:
| Type | Description |
|---|---|
ValueError
|
If an array has the wrong shape or dtype. |
Source code in src/simscope/core.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
Scene
dataclass
¶
The static part of a rollout, shared by every frame and env.
Attributes:
| Name | Type | Description |
|---|---|---|
bodies |
tuple[Body, ...]
|
Bodies, in the order of the pose stream. |
geoms |
tuple[Geom, ...]
|
Geoms attached to bodies. |
materials |
tuple[Material, ...]
|
Materials referenced by geoms. Must not be empty when there are geoms. |
meshes |
tuple[Mesh, ...]
|
Meshes referenced by mesh geoms. |
textures |
tuple[Texture, ...]
|
Textures referenced by materials. |
Source code in src/simscope/core.py
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | |
n_bodies: int
property
¶
Number of bodies, which is the pose count per frame.
__post_init__() -> None
¶
Validates cross-references between scene elements.
Raises:
| Type | Description |
|---|---|
ValueError
|
If an index points outside its table, or a mesh geom has no mesh. |
Source code in src/simscope/core.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | |
Texture
dataclass
¶
An encoded 2D image.
Attributes:
| Name | Type | Description |
|---|---|---|
data |
bytes
|
The encoded file bytes. |
media_type |
Literal['image/png', 'image/jpeg']
|
|
width |
int
|
Width in pixels. |
height |
int
|
Height in pixels. |
Source code in src/simscope/core.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
simscope.transforms
¶
Vectorized quaternion and pose math.
Quaternions are xyzw. Poses are [..., 7] arrays of
position xyz followed by quaternion xyzw. Every function broadcasts over
leading axes and never loops in Python.
compose_poses(parent: FloatArray, child: FloatArray) -> FloatArray
¶
Composes poses: expresses child (given in parent) in world.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parent
|
FloatArray
|
Poses with shape |
required |
child
|
FloatArray
|
Poses relative to |
required |
Returns:
| Type | Description |
|---|---|
FloatArray
|
World poses with the broadcast shape |
Source code in src/simscope/transforms.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 | |
enforce_sign_continuity(quat: FloatArray, previous: FloatArray | None = None) -> FloatArray
¶
Flips quaternion signs so consecutive frames stay in one hemisphere.
q and -q are the same rotation. Keeping dot(q[t], q[t-1]) >= 0
makes time deltas small, which the block codecs rely on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quat
|
FloatArray
|
Quaternions with shape |
required |
previous
|
FloatArray | None
|
The quaternion frame just before |
None
|
Returns:
| Type | Description |
|---|---|
FloatArray
|
A new array with the same shape, with signs flipped where needed. |
Source code in src/simscope/transforms.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
quat_mul(a: FloatArray, b: FloatArray) -> FloatArray
¶
Multiplies quaternions a * b (apply b, then a).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
FloatArray
|
Quaternions with shape |
required |
b
|
FloatArray
|
Quaternions with shape |
required |
Returns:
| Type | Description |
|---|---|
FloatArray
|
The products with the broadcast shape, xyzw. |
Source code in src/simscope/transforms.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
quat_rotate(quat: FloatArray, vec: FloatArray) -> FloatArray
¶
Rotates vectors by unit quaternions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quat
|
FloatArray
|
Unit quaternions with shape |
required |
vec
|
FloatArray
|
Vectors with shape |
required |
Returns:
| Type | Description |
|---|---|
FloatArray
|
The rotated vectors with the broadcast shape. |
Source code in src/simscope/transforms.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 | |
quat_to_matrix(quat: FloatArray) -> FloatArray
¶
Converts unit quaternions to rotation matrices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quat
|
FloatArray
|
Unit quaternions with shape |
required |
Returns:
| Type | Description |
|---|---|
FloatArray
|
Rotation matrices with shape |
Source code in src/simscope/transforms.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
wxyz_to_xyzw(quat: npt.ArrayLike) -> npt.NDArray[np.float32]
¶
Reorders quaternions from wxyz to xyzw.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quat
|
ArrayLike
|
Quaternions with shape |
required |
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A new float32 array with shape |
Source code in src/simscope/transforms.py
14 15 16 17 18 19 20 21 22 23 24 | |
xyzw_to_wxyz(quat: npt.ArrayLike) -> npt.NDArray[np.float32]
¶
Reorders quaternions from xyzw to wxyz.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quat
|
ArrayLike
|
Quaternions with shape |
required |
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A new float32 array with shape |
Source code in src/simscope/transforms.py
27 28 29 30 31 32 33 34 35 36 37 | |
Storage¶
simscope.io is the format v1 implementation: block files, the
content-addressed store, manifests, and packs. Most recordings go through
Library and never call this layer.
simscope.io
¶
On-disk format shared by the recorder and the player.
Modules:
| Name | Description |
|---|---|
codecs |
Block payload codecs (f32s, q16d) and mesh blobs (raw, q16). |
blockfile |
|
cas |
|
scene |
Canonical scene descriptors ( |
manifest |
|
pack |
|
BlockReader
¶
Random-access reader for a block file.
The file is memory-mapped. read decodes only the blocks that overlap
the requested window and keeps the most recent decoded blocks in an LRU
cache. Instances are safe to share between threads.
With partial=True the reader can also tail a file that a
:class:BlockWriter is still writing: it builds the
directory by scanning blocks and :meth:refresh picks up windows
appended later. It never writes to the file, and only complete windows
whose CRCs check out are visible, so a window the writer is in the middle
of writing is never returned.
Attributes:
| Name | Type | Description |
|---|---|---|
n_frames |
Total frames (the complete windows found so far, for a file that is still being written). |
|
n_envs |
Number of envs. |
|
item_shape |
Shape of one env-frame item. |
|
block_frames |
Nominal frames per block. |
|
n_blocks |
Number of blocks. |
|
directory |
Structured array of directory entries ( |
Source code in src/simscope/io/blockfile.py
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 | |
finished: bool
property
¶
True if the file has its directory (it will not grow).
__enter__() -> BlockReader
¶
Returns the reader.
Source code in src/simscope/io/blockfile.py
769 770 771 | |
__exit__(exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None
¶
Closes the reader.
Source code in src/simscope/io/blockfile.py
773 774 775 776 777 778 779 780 | |
__init__(source: os.PathLike[str] | str | bytes | bytearray | memoryview, *, cache_blocks: int = 64, kind: core.StreamKind | None = None, verify: bool = True, partial: bool = False) -> None
¶
Opens a block file or an in-memory block file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
PathLike[str] | str | bytes | bytearray | memoryview
|
A path, or the file bytes (for example a pack entry). |
required |
cache_blocks
|
int
|
Decoded blocks to keep in the LRU cache. |
64
|
kind
|
StreamKind | None
|
The stream kind from the manifest. For |
None
|
verify
|
bool
|
Check each block's payload CRC when it is decoded. |
True
|
partial
|
bool
|
Accept an unfinished file ( |
False
|
Raises:
| Type | Description |
|---|---|
FormatError
|
If the file is unfinished and |
Source code in src/simscope/io/blockfile.py
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 | |
close() -> None
¶
Releases the memory map. Safe to call more than once.
Source code in src/simscope/io/blockfile.py
759 760 761 762 763 764 765 766 767 | |
read(t0: int, t1: int, envs: Sequence[int] | npt.NDArray[np.integer] | None = None) -> npt.NDArray[np.float32]
¶
Reads frames t0 <= t < t1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t0
|
int
|
First frame. |
required |
t1
|
int
|
One past the last frame. |
required |
envs
|
Sequence[int] | NDArray[integer] | None
|
Env indices to read, or |
None
|
Returns:
| Type | Description |
|---|---|
NDArray[float32]
|
A new float32 array |
Raises:
| Type | Description |
|---|---|
IndexError
|
If the frame range or an env index is out of range. |
ValueError
|
If the reader is closed. |
Source code in src/simscope/io/blockfile.py
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 | |
refresh() -> int
¶
Picks up windows appended to a file that is still being written.
Re-reads the header and scans only the bytes after the last known good window, so the cost is proportional to the new data. When the writer has finalized the file, the reader switches to the finished directory. A finished file, or an in-memory source, is left as is.
Returns:
| Type | Description |
|---|---|
int
|
The number of frames now readable. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the file shrank, or a finished file has an invalid directory. |
ValueError
|
If the reader is closed. |
Source code in src/simscope/io/blockfile.py
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 | |
BlockWriter
¶
Writes one stream as a block file.
append copies frames into a preallocated [block_frames, n_envs, K]
buffer. Each time it fills, the window is encoded (one block per env) and
written. Leaving the with block normally calls :meth:finalize; on an
exception the file is closed unfinished so :func:recover can salvage it.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
Destination path. |
|
item_shape |
Shape of one env-frame item. |
|
n_envs |
Number of envs. |
|
kind |
Stream kind ( |
|
codec |
Requested codec name. |
|
block_frames |
Frames per block. |
Source code in src/simscope/io/blockfile.py
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 | |
n_frames: int
property
¶
Frames appended so far, including the unflushed window.
__enter__() -> BlockWriter
¶
Returns the writer.
Source code in src/simscope/io/blockfile.py
414 415 416 | |
__exit__(exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None
¶
Finalizes on success; on error closes and leaves it unfinished.
Source code in src/simscope/io/blockfile.py
418 419 420 421 422 423 424 425 426 427 428 | |
__init__(path: os.PathLike[str] | str, *, item_shape: Sequence[int], n_envs: int, kind: core.StreamKind, codec: str = 'f32s', block_frames: int = DEFAULT_BLOCK_FRAMES) -> None
¶
Creates the file and writes a provisional header.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathLike[str] | str
|
Destination path (overwritten). |
required |
item_shape
|
Sequence[int]
|
Shape of one env-frame item, at most 4 dims, all > 0. |
required |
n_envs
|
int
|
Number of envs (at least 1). |
required |
kind
|
StreamKind
|
Stream kind. |
required |
codec
|
str
|
|
'f32s'
|
block_frames
|
int
|
Frames per block (at least 1). |
DEFAULT_BLOCK_FRAMES
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If an argument is invalid. |
FormatError
|
If the codec is unknown. |
Source code in src/simscope/io/blockfile.py
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | |
append(frames: npt.ArrayLike) -> None
¶
Appends frames to the stream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frames
|
ArrayLike
|
Array |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the shape is wrong or the writer is finalized. |
Source code in src/simscope/io/blockfile.py
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | |
close() -> None
¶
Closes the file without finalizing (the file stays recoverable).
Source code in src/simscope/io/blockfile.py
408 409 410 411 412 | |
finalize() -> None
¶
Flushes the last window, writes the directory and the header.
Source code in src/simscope/io/blockfile.py
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 | |
write_encoded(t0: int, blocks: Sequence[EncodedBlock]) -> None
¶
Writes already-encoded blocks of one window.
Advances the frame count, so finalize records it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t0
|
int
|
First frame index of the window. |
required |
blocks
|
Sequence[EncodedBlock]
|
One encoded block per env, in env order. |
required |
Source code in src/simscope/io/blockfile.py
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
ContentStore
¶
A directory of immutable files named by their SHA-256.
Assets live at assets/<h0h1>/<hex> and scene descriptors at
scenes/<h0h1>/<hex>.json under the library root.
Attributes:
| Name | Type | Description |
|---|---|---|
root |
The library root directory. |
Source code in src/simscope/io/cas.py
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | |
__init__(root: os.PathLike[str] | str) -> None
¶
Creates a store handle. Nothing is created on disk until a put.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
PathLike[str] | str
|
The library root. |
required |
Source code in src/simscope/io/cas.py
218 219 220 221 222 223 224 | |
get(ref: Ref, kind: Kind = 'asset', *, verify: bool = False) -> bytes
¶
Reads a file and checks its size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ref
|
Ref
|
The content reference. |
required |
kind
|
Kind
|
|
'asset'
|
verify
|
bool
|
Also check the SHA-256. |
False
|
Returns:
| Type | Description |
|---|---|
bytes
|
The file contents. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the file is missing. |
FormatError
|
If the size or hash does not match. |
Source code in src/simscope/io/cas.py
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | |
has(ref: Ref, kind: Kind = 'asset') -> bool
¶
Tells whether a file is present.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ref
|
Ref
|
The content reference. |
required |
kind
|
Kind
|
|
'asset'
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the file exists. |
Source code in src/simscope/io/cas.py
257 258 259 260 261 262 263 264 265 266 267 | |
path(ref: Ref, kind: Kind = 'asset') -> pathlib.Path
¶
Returns the file path for a reference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ref
|
Ref
|
The content reference. |
required |
kind
|
Kind
|
|
'asset'
|
Returns:
| Type | Description |
|---|---|
Path
|
The path, which may not exist. |
Source code in src/simscope/io/cas.py
226 227 228 229 230 231 232 233 234 235 236 237 | |
put(data: bytes | bytearray | memoryview, kind: Kind = 'asset') -> Ref
¶
Stores bytes, skipping the write if the target already exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes | bytearray | memoryview
|
The file contents. |
required |
kind
|
Kind
|
|
'asset'
|
Returns:
| Type | Description |
|---|---|
Ref
|
The reference to the stored file. |
Source code in src/simscope/io/cas.py
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | |
PackReader
¶
Read access to a pack, from a file (memory-mapped) or from bytes.
read returns zero-copy memoryview slices of the pack. Close readers
opened with :meth:stream before closing the pack.
Source code in src/simscope/io/pack.py
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 | |
__contains__(path: object) -> bool
¶
Tells whether an entry exists.
Source code in src/simscope/io/pack.py
501 502 503 | |
__enter__() -> PackReader
¶
Returns the reader.
Source code in src/simscope/io/pack.py
596 597 598 | |
__exit__(exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None
¶
Closes the reader.
Source code in src/simscope/io/pack.py
600 601 602 603 604 605 606 607 | |
__init__(source: os.PathLike[str] | str | bytes | bytearray | memoryview) -> None
¶
Opens a pack and reads its directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
PathLike[str] | str | bytes | bytearray | memoryview
|
A path, or the pack bytes. |
required |
Raises:
| Type | Description |
|---|---|
FormatError
|
On a bad magic, unknown major version, CRC mismatch or invalid directory. |
Source code in src/simscope/io/pack.py
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 | |
close() -> None
¶
Releases the memory map. Safe to call more than once.
Source code in src/simscope/io/pack.py
585 586 587 588 589 590 591 592 593 594 | |
manifest(run: str) -> manifest.RolloutManifest
¶
Parses a run's manifest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str
|
Run name. |
required |
Returns:
| Type | Description |
|---|---|
RolloutManifest
|
The manifest. |
Source code in src/simscope/io/pack.py
532 533 534 535 536 537 538 539 540 541 542 | |
paths() -> list[str]
¶
Returns all entry paths, sorted.
Source code in src/simscope/io/pack.py
497 498 499 | |
read(path: str) -> memoryview
¶
Returns an entry's bytes as a zero-copy view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Entry path, for example |
required |
Returns:
| Type | Description |
|---|---|
memoryview
|
A read-only memoryview into the pack. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the entry does not exist. |
ValueError
|
If the reader is closed. |
Source code in src/simscope/io/pack.py
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 | |
runs() -> list[str]
¶
Returns the names of the runs in the pack, sorted.
Source code in src/simscope/io/pack.py
523 524 525 526 527 528 529 530 | |
scene(ref: cas.Ref) -> core.Scene
¶
Loads a scene and its assets from the pack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ref
|
Ref
|
Reference from a manifest ( |
required |
Returns:
| Type | Description |
|---|---|
Scene
|
The scene. Transcoded meshes have no normals. |
Source code in src/simscope/io/pack.py
551 552 553 554 555 556 557 558 559 560 561 562 | |
stream(run: str, name: str, *, cache_blocks: int = 64) -> blockfile.BlockReader
¶
Opens a stream of a run for random access.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run
|
str
|
Run name. |
required |
name
|
str
|
Stream name from the run's manifest. |
required |
cache_blocks
|
int
|
Decoded blocks to cache. |
64
|
Returns:
| Type | Description |
|---|---|
BlockReader
|
A reader over the block file inside the pack, with the stream |
BlockReader
|
kind set so q16d poses are renormalized. |
Source code in src/simscope/io/pack.py
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 | |
Ref
dataclass
¶
A reference to a content-addressed file.
Attributes:
| Name | Type | Description |
|---|---|---|
sha256 |
str
|
64-character lowercase hex SHA-256 of the file bytes. |
size |
int
|
File size in bytes. |
Source code in src/simscope/io/cas.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
__post_init__() -> None
¶
Validates the hash and size.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/simscope/io/cas.py
37 38 39 40 41 42 43 44 45 46 47 | |
from_json(obj: dict[str, Any]) -> Ref
classmethod
¶
Parses a reference object, ignoring unknown fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
dict[str, Any]
|
A mapping with |
required |
Returns:
| Type | Description |
|---|---|
Ref
|
The reference. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If a field is missing or malformed. |
Source code in src/simscope/io/cas.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
of(data: bytes | bytearray | memoryview) -> Ref
classmethod
¶
Computes the reference of some bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes | bytearray | memoryview
|
The file contents. |
required |
Returns:
| Type | Description |
|---|---|
Ref
|
The reference. |
Source code in src/simscope/io/cas.py
71 72 73 74 75 76 77 78 79 80 81 | |
to_json() -> dict[str, Any]
¶
Returns the spec's {"sha256", "size"} object.
Source code in src/simscope/io/cas.py
49 50 51 | |
RolloutManifest
dataclass
¶
The manifest of one rollout.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
ULID, fixed at creation. |
name |
str
|
Run name, equal to the run directory name. |
created |
str
|
UTC creation time, |
status |
str
|
|
dt |
float
|
Seconds between frames. |
n_frames |
int
|
Frames per stream. |
n_envs |
int
|
Envs per stream. |
n_bodies |
int
|
Bodies per pose. |
scene |
Ref
|
Reference to the scene descriptor. |
streams |
dict[str, StreamInfo]
|
Streams by name. |
env_scenes |
tuple[Ref, ...] | None
|
Optional per-env scene references. |
env_origins |
tuple[Vec3, ...] | None
|
World offset per env, |
source |
dict[str, Any]
|
Provenance (simulator and version). |
tags |
tuple[str, ...]
|
Free-form tags set at record time. |
meta |
dict[str, Any]
|
Free-form metadata set at record time. |
Source code in src/simscope/io/manifest.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
from_json(obj: dict[str, Any]) -> RolloutManifest
classmethod
¶
Parses and validates a manifest, ignoring unknown fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
dict[str, Any]
|
The parsed |
required |
Returns:
| Type | Description |
|---|---|
RolloutManifest
|
The manifest. |
Raises:
| Type | Description |
|---|---|
FormatError
|
On an unknown format or major version, a
missing field, or an invalid |
Source code in src/simscope/io/manifest.py
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
to_json() -> dict[str, Any]
¶
Returns the JSON object of this manifest.
Source code in src/simscope/io/manifest.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
validate() -> None
¶
Checks internal consistency.
Raises:
| Type | Description |
|---|---|
FormatError
|
If the run name, status, env data or the
required |
Source code in src/simscope/io/manifest.py
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
StreamInfo
dataclass
¶
One stream entry of a manifest.
Attributes:
| Name | Type | Description |
|---|---|---|
file |
str
|
Block file name inside the run directory. |
kind |
StreamKind
|
How viewers draw the stream. |
item_shape |
tuple[int, ...]
|
Shape of one env-frame item. |
labels |
tuple[str, ...] | None
|
Optional names for vector components. |
units |
str | None
|
Optional unit string. |
scale |
float | None
|
Optional metres drawn per unit of an |
Source code in src/simscope/io/manifest.py
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
from_json(obj: dict[str, Any]) -> StreamInfo
classmethod
¶
Parses a stream object, ignoring unknown fields.
Raises:
| Type | Description |
|---|---|
FormatError
|
If a field is missing or invalid. |
Source code in src/simscope/io/manifest.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
to_json() -> dict[str, Any]
¶
Returns the JSON object of this stream.
Source code in src/simscope/io/manifest.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
load_scene(store: cas.ContentStore, ref: cas.Ref) -> core.Scene
¶
Loads a scene and its assets from a content store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
ContentStore
|
The content store of the library. |
required |
ref
|
Ref
|
Reference of the scene JSON. |
required |
Returns:
| Type | Description |
|---|---|
Scene
|
The scene. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the scene or an asset is malformed. |
FileNotFoundError
|
If a referenced file is missing. |
Source code in src/simscope/io/scene.py
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | |
new_ulid(now_ms: int | None = None) -> str
¶
Generates a ULID: 48-bit ms timestamp plus 80 random bits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
now_ms
|
int | None
|
Unix time in milliseconds, or |
None
|
Returns:
| Type | Description |
|---|---|
str
|
26 Crockford base32 characters. |
Source code in src/simscope/io/manifest.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | |
put_scene(store: cas.ContentStore, scene: core.Scene) -> cas.Ref
¶
Stores a scene: raw mesh blobs, texture blobs, then the descriptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
store
|
ContentStore
|
The content store of the library. |
required |
scene
|
Scene
|
The scene to store. |
required |
Returns:
| Type | Description |
|---|---|
Ref
|
The reference of the canonical scene JSON under |
Ref
|
scenes give equal references. |
Source code in src/simscope/io/scene.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | |
read_manifest(run_dir: os.PathLike[str] | str) -> RolloutManifest
¶
Reads a run's manifest.
Prefers rollout.json; falls back to rollout.json.partial for a
run that is still recording (or crashed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_dir
|
PathLike[str] | str
|
The run directory. |
required |
Returns:
| Type | Description |
|---|---|
RolloutManifest
|
The manifest. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If neither file exists. |
FormatError
|
If the manifest is invalid. |
Source code in src/simscope/io/manifest.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | |
recover(path: os.PathLike[str] | str) -> RecoverResult
¶
Rebuilds the directory of an unfinished block file.
Walks blocks from offset 64, keeps every complete time window (a block for every env) whose header and CRC check out, truncates the rest, then appends the directory and rewrites the header. A finished file is left untouched.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathLike[str] | str
|
The block file. |
required |
Returns:
| Type | Description |
|---|---|
RecoverResult
|
What was kept and dropped. |
Raises:
| Type | Description |
|---|---|
FormatError
|
If the file header is missing or corrupt. |
Source code in src/simscope/io/blockfile.py
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 | |
validate_run_name(name: str) -> str
¶
Checks a run name against ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Candidate run name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The name, unchanged. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the name is not valid. |
Source code in src/simscope/io/manifest.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
write_manifest(run_dir: os.PathLike[str] | str, manifest: RolloutManifest, *, partial: bool) -> pathlib.Path
¶
Writes a manifest atomically.
With partial=True the file is rollout.json.partial and the status
must be "recording". With partial=False the file is
rollout.json, the status must be "complete", and any .partial
file is deleted afterwards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_dir
|
PathLike[str] | str
|
The run directory (created if missing). |
required |
manifest
|
RolloutManifest
|
The manifest. |
required |
partial
|
bool
|
Whether this is an in-progress manifest. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The path written. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the status does not match |
FormatError
|
If the manifest is inconsistent. |
Source code in src/simscope/io/manifest.py
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | |
write_pack(library_root: os.PathLike[str] | str, run_names: list[str], out_path: os.PathLike[str] | str, *, transcode: bool = True, annotations: bool = True, posters: bool = True, envs: Sequence[int] | None = None, derived: Mapping[str, Source] | None = None) -> pathlib.Path
¶
Writes runs and everything they reference into one pack file.
With transcode=True (the export default), pose streams are re-encoded
with q16d, raw meshes are re-encoded as q16, and scenes are rewritten
with the new mesh references (so their hashes change). Other streams and
textures are copied unchanged. Each asset is stored once, however many
runs share it. The library is not modified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_root
|
PathLike[str] | str
|
The library directory, or a folder that contains
libraries. A run is taken from the nested library that holds it
when it is not a direct child of |
required |
run_names
|
list[str]
|
Runs to include (each must be complete). |
required |
out_path
|
PathLike[str] | str
|
The pack file to write (replaced atomically). |
required |
transcode
|
bool
|
Whether to shrink the pack as described above. |
True
|
annotations
|
bool
|
Whether to include each run's |
True
|
posters
|
bool
|
Whether to include each run's |
True
|
envs
|
Sequence[int] | None
|
Keep only these envs of every run, in this order. Streams are
re-encoded for them (the source codec, or q16d poses with
|
None
|
derived
|
Mapping[str, Source] | None
|
Extra entries by pack path, all under |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
The output path. |
Raises:
| Type | Description |
|---|---|
ValueError
|
On duplicate run names, a run that is still recording,
an invalid |
FileNotFoundError
|
If a run or referenced file is missing. |
Source code in src/simscope/io/pack.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | |