sublinear 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sublinear-0.2.0 → sublinear-0.2.2}/PKG-INFO +1 -1
- {sublinear-0.2.0 → sublinear-0.2.2}/pyproject.toml +1 -1
- {sublinear-0.2.0 → sublinear-0.2.2}/src/sublinear/core.py +294 -2
- {sublinear-0.2.0 → sublinear-0.2.2}/src/sublinear.egg-info/PKG-INFO +1 -1
- {sublinear-0.2.0 → sublinear-0.2.2}/README.md +0 -0
- {sublinear-0.2.0 → sublinear-0.2.2}/setup.cfg +0 -0
- {sublinear-0.2.0 → sublinear-0.2.2}/src/sublinear/__init__.py +0 -0
- {sublinear-0.2.0 → sublinear-0.2.2}/src/sublinear.egg-info/SOURCES.txt +0 -0
- {sublinear-0.2.0 → sublinear-0.2.2}/src/sublinear.egg-info/dependency_links.txt +0 -0
- {sublinear-0.2.0 → sublinear-0.2.2}/src/sublinear.egg-info/requires.txt +0 -0
- {sublinear-0.2.0 → sublinear-0.2.2}/src/sublinear.egg-info/top_level.txt +0 -0
|
@@ -397,6 +397,298 @@ class Sublinear:
|
|
|
397
397
|
verbose: bool = True,
|
|
398
398
|
**input_json: Any,
|
|
399
399
|
) -> SimpleNamespace | list[SimpleNamespace]:
|
|
400
|
-
print('Thank you for trying out Sublinear! We are still in development and will be launching the API soon.')
|
|
401
|
-
return
|
|
400
|
+
# print('Thank you for trying out Sublinear! We are still in development and will be launching the API soon.')
|
|
401
|
+
# return
|
|
402
|
+
"""
|
|
403
|
+
Create one inference job for each video.
|
|
404
|
+
All keyword arguments captured by **input_json are passed
|
|
405
|
+
to every inference job. Each job also receives the view_type
|
|
406
|
+
associated with that video.
|
|
407
|
+
"""
|
|
408
|
+
if "downsample_to" not in input_json:
|
|
409
|
+
input_json["downsample_to"] = 10
|
|
410
|
+
if "task_description" not in input_json:
|
|
411
|
+
raise ValueError("task_description must be provided")
|
|
412
|
+
if "verbose" not in input_json:
|
|
413
|
+
input_json["verbose"] = True
|
|
414
|
+
#
|
|
415
|
+
if isinstance(video_paths, str):
|
|
416
|
+
video_paths = [video_paths]
|
|
417
|
+
if isinstance(video_frames, np.ndarray):
|
|
418
|
+
video_frames = list(video_frames)
|
|
419
|
+
if isinstance(video_frames_external_view, np.ndarray):
|
|
420
|
+
video_frames_external_view = list(video_frames_external_view)
|
|
421
|
+
if isinstance(video_frames_wrist_view, np.ndarray):
|
|
422
|
+
video_frames_wrist_view = list(video_frames_wrist_view)
|
|
423
|
+
#
|
|
424
|
+
videos, final_video_paths, final_video_paths_external_view, final_video_paths_wrist_view, view_type_per_video, single_video, video_count = get_final_video_frames(
|
|
425
|
+
model=model,
|
|
426
|
+
video_paths=video_paths,
|
|
427
|
+
video_path=video_path,
|
|
428
|
+
video_paths_external_view=(video_paths_external_view),
|
|
429
|
+
video_paths_wrist_view=(video_paths_wrist_view),
|
|
430
|
+
video_path_external_view=(video_path_external_view),
|
|
431
|
+
video_path_wrist_view=(video_path_wrist_view),
|
|
432
|
+
video_frames=video_frames,
|
|
433
|
+
video_frames_external_view=(video_frames_external_view),
|
|
434
|
+
video_frames_wrist_view=(video_frames_wrist_view),
|
|
435
|
+
view_type_per_video=view_type_per_video,
|
|
436
|
+
view_type=view_type,
|
|
437
|
+
verbose=verbose,
|
|
438
|
+
)
|
|
439
|
+
#
|
|
440
|
+
if video_count < 1 or video_count > 100:
|
|
441
|
+
raise ValueError(f"Number of videos must be between 1 and 100, got {video_count}")
|
|
442
|
+
#
|
|
443
|
+
videos_iterable = (enumerate(videos) if videos is not None else repeat(None, video_count))
|
|
444
|
+
final_video_paths_iterable = (enumerate(final_video_paths) if final_video_paths is not None else repeat(None, video_count))
|
|
445
|
+
final_video_paths_external_view_iterable = (enumerate(final_video_paths_external_view) if final_video_paths_external_view is not None else repeat(None, video_count))
|
|
446
|
+
final_video_paths_wrist_view_iterable = (enumerate(final_video_paths_wrist_view) if final_video_paths_wrist_view is not None else repeat(None, video_count))
|
|
447
|
+
model = model.lower()
|
|
448
|
+
# if "dm" not in model and model not in ["sole-r1", "robometer", "roboreward", "topreward"]:
|
|
449
|
+
# raise ValueError(
|
|
450
|
+
# f"Model '{model}' is not supported. "
|
|
451
|
+
# "Supported models are: 'sole-r1', 'robometer', 'roboreward', 'topreward'."
|
|
452
|
+
# )
|
|
453
|
+
active_job_ids: set[str] = set()
|
|
454
|
+
active_job_ids_lock = threading.Lock()
|
|
455
|
+
cancel_requested = threading.Event()
|
|
456
|
+
def register_job(job_id: str) -> None:
|
|
457
|
+
with active_job_ids_lock:
|
|
458
|
+
active_job_ids.add(job_id)
|
|
459
|
+
# Handles a race where Ctrl+C occurs just before registration.
|
|
460
|
+
if cancel_requested.is_set():
|
|
461
|
+
try:
|
|
462
|
+
self.cancel_job(job_id)
|
|
463
|
+
except requests.RequestException:
|
|
464
|
+
pass
|
|
465
|
+
def unregister_job(job_id: str) -> None:
|
|
466
|
+
with active_job_ids_lock:
|
|
467
|
+
active_job_ids.discard(job_id)
|
|
468
|
+
def submit_video(
|
|
469
|
+
indexed_video: tuple[int, list[np.ndarray]],
|
|
470
|
+
indexed_video_path: tuple[int, str] | None = None,
|
|
471
|
+
indexed_video_path_external_view: tuple[int, str] | None = None,
|
|
472
|
+
indexed_video_path_wrist_view: tuple[int, str] | None = None,
|
|
473
|
+
) -> SimpleNamespace:
|
|
474
|
+
if indexed_video is None and (indexed_video_path_external_view is None or indexed_video_path_wrist_view is None):
|
|
475
|
+
if not indexed_video_path is None:
|
|
476
|
+
video_index, video_path = indexed_video_path
|
|
477
|
+
# frames = load_video_frames(video_path)
|
|
478
|
+
elif not indexed_video_path_external_view is None and indexed_video_path_wrist_view is None:
|
|
479
|
+
video_index, video_path = indexed_video_path_external_view
|
|
480
|
+
# frames = load_video_frames(video_path_external_view)
|
|
481
|
+
elif indexed_video_path_external_view is None and not indexed_video_path_wrist_view is None:
|
|
482
|
+
video_index, video_path_wrist_view = indexed_video_path_wrist_view
|
|
483
|
+
# frames = load_video_frames(video_path_wrist_view)
|
|
484
|
+
#
|
|
485
|
+
source_path = Path(video_path)
|
|
486
|
+
job_input_json = dict(input_json)
|
|
487
|
+
frame_width, frame_height = get_video_dimensions(
|
|
488
|
+
source_path
|
|
489
|
+
)
|
|
490
|
+
job_input_json["_frame_width"] = frame_width
|
|
491
|
+
job_input_json["_frame_height"] = frame_height
|
|
492
|
+
job_input_json["view_type"] = view_type_per_video[video_index]
|
|
493
|
+
with source_path.open("rb") as video_file:
|
|
494
|
+
created_job = self.create_job(
|
|
495
|
+
job_input_json,
|
|
496
|
+
video=video_file,
|
|
497
|
+
filename=source_path.name,
|
|
498
|
+
model=model,
|
|
499
|
+
on_job_created=register_job,
|
|
500
|
+
)
|
|
501
|
+
else:
|
|
502
|
+
if not indexed_video is None:
|
|
503
|
+
video_index, frames = indexed_video
|
|
504
|
+
elif not indexed_video_path_external_view is None and not indexed_video_path_wrist_view is None:
|
|
505
|
+
video_index, video_path_external_view = indexed_video_path_external_view
|
|
506
|
+
video_index, video_path_wrist_view = indexed_video_path_wrist_view
|
|
507
|
+
frames_external = load_video_frames(video_path_external_view)
|
|
508
|
+
frames_wrist = load_video_frames(video_path_wrist_view)
|
|
509
|
+
if not len(frames_external) == len(frames_wrist):
|
|
510
|
+
raise ValueError(f"Number of frames in external view video {len(frames_external)} does not match number of frames in wrist view video {len(frames_wrist)} for video_idx {video_index}")
|
|
511
|
+
frames = [np.concatenate((frames_external[i], frames_wrist[i]), axis=1) for i in range(len(frames_external))]
|
|
512
|
+
else:
|
|
513
|
+
raise ValueError("No video data provided for submission.")
|
|
514
|
+
#
|
|
515
|
+
if not frames:
|
|
516
|
+
raise ValueError(
|
|
517
|
+
f"Video {video_index} has no frames."
|
|
518
|
+
)
|
|
519
|
+
first_frame = np.asarray(frames[0])
|
|
520
|
+
if (
|
|
521
|
+
first_frame.ndim != 3
|
|
522
|
+
or first_frame.shape[2] != 3
|
|
523
|
+
):
|
|
524
|
+
raise ValueError(
|
|
525
|
+
f"Video {video_index} frames must have "
|
|
526
|
+
"shape (height, width, 3)."
|
|
527
|
+
)
|
|
528
|
+
height, width = first_frame.shape[:2]
|
|
529
|
+
filename = f"video_{video_index}_{time.time_ns()}.mp4"
|
|
530
|
+
job_input_json = dict(input_json)
|
|
531
|
+
job_input_json["_frame_width"] = width
|
|
532
|
+
job_input_json["_frame_height"] = height
|
|
533
|
+
job_input_json["view_type"] = (view_type_per_video[video_index])
|
|
534
|
+
with tempfile.TemporaryDirectory() as temp_directory:
|
|
535
|
+
encoded_video_path = (
|
|
536
|
+
Path(temp_directory) / filename
|
|
537
|
+
)
|
|
538
|
+
writer = cv2.VideoWriter(
|
|
539
|
+
str(encoded_video_path),
|
|
540
|
+
cv2.VideoWriter_fourcc(*"mp4v"),
|
|
541
|
+
30.0,
|
|
542
|
+
(width, height),
|
|
543
|
+
)
|
|
544
|
+
if not writer.isOpened():
|
|
545
|
+
raise RuntimeError(
|
|
546
|
+
f"Unable to initialize MP4 encoder "
|
|
547
|
+
f"for video {video_index}."
|
|
548
|
+
)
|
|
549
|
+
try:
|
|
550
|
+
for frame_index, frame in enumerate(frames):
|
|
551
|
+
frame = np.asarray(frame)
|
|
552
|
+
if (
|
|
553
|
+
frame.ndim != 3
|
|
554
|
+
or frame.shape != first_frame.shape
|
|
555
|
+
):
|
|
556
|
+
raise ValueError(
|
|
557
|
+
f"Frame {frame_index} of video "
|
|
558
|
+
f"{video_index} has shape "
|
|
559
|
+
f"{frame.shape}; expected "
|
|
560
|
+
f"{first_frame.shape}."
|
|
561
|
+
)
|
|
562
|
+
if frame.dtype != np.uint8:
|
|
563
|
+
if (
|
|
564
|
+
np.issubdtype(
|
|
565
|
+
frame.dtype,
|
|
566
|
+
np.floating,
|
|
567
|
+
)
|
|
568
|
+
and frame.size > 0
|
|
569
|
+
and frame.min() >= 0
|
|
570
|
+
and frame.max() <= 1
|
|
571
|
+
):
|
|
572
|
+
frame = frame * 255
|
|
573
|
+
frame = np.clip(
|
|
574
|
+
frame,
|
|
575
|
+
0,
|
|
576
|
+
255,
|
|
577
|
+
).astype(np.uint8)
|
|
578
|
+
# Input frames are RGB; OpenCV expects BGR.
|
|
579
|
+
bgr_frame = cv2.cvtColor(
|
|
580
|
+
frame,
|
|
581
|
+
cv2.COLOR_RGB2BGR,
|
|
582
|
+
)
|
|
583
|
+
writer.write(bgr_frame)
|
|
584
|
+
finally:
|
|
585
|
+
writer.release()
|
|
586
|
+
if (
|
|
587
|
+
not encoded_video_path.is_file()
|
|
588
|
+
or encoded_video_path.stat().st_size == 0
|
|
589
|
+
):
|
|
590
|
+
raise RuntimeError(
|
|
591
|
+
f"Encoding video {video_index} failed."
|
|
592
|
+
)
|
|
593
|
+
with encoded_video_path.open("rb") as video_file:
|
|
594
|
+
created_job = self.create_job(
|
|
595
|
+
job_input_json,
|
|
596
|
+
video=video_file,
|
|
597
|
+
filename=filename,
|
|
598
|
+
model=model,
|
|
599
|
+
on_job_created=register_job,
|
|
600
|
+
)
|
|
601
|
+
#
|
|
602
|
+
job_id = created_job["job_id"]
|
|
603
|
+
try:
|
|
604
|
+
finished_job = self.wait_for_job(job_id)
|
|
605
|
+
finally:
|
|
606
|
+
# completed, failed, or canceled are all terminal.
|
|
607
|
+
unregister_job(job_id)
|
|
608
|
+
result = finished_job.get("result")
|
|
609
|
+
if not isinstance(result, dict):
|
|
610
|
+
raise RuntimeError(
|
|
611
|
+
f"Video {video_index} returned an "
|
|
612
|
+
"invalid inference result."
|
|
613
|
+
)
|
|
614
|
+
return SimpleNamespace(**result)
|
|
615
|
+
#
|
|
616
|
+
worker_count = min(
|
|
617
|
+
self.max_parallel_jobs,
|
|
618
|
+
# len(videos),
|
|
619
|
+
video_count,
|
|
620
|
+
)
|
|
621
|
+
executor = ThreadPoolExecutor(max_workers=worker_count)
|
|
622
|
+
try:
|
|
623
|
+
results = list(executor.map(submit_video, videos_iterable, final_video_paths_iterable, final_video_paths_external_view_iterable, final_video_paths_wrist_view_iterable,))
|
|
624
|
+
except KeyboardInterrupt:
|
|
625
|
+
cancel_requested.set()
|
|
626
|
+
with active_job_ids_lock:
|
|
627
|
+
jobs_to_cancel = list(active_job_ids)
|
|
628
|
+
for job_id in jobs_to_cancel:
|
|
629
|
+
try:
|
|
630
|
+
self.cancel_job(job_id)
|
|
631
|
+
except requests.RequestException as error:
|
|
632
|
+
print(
|
|
633
|
+
f"Could not cancel job {job_id}: {error}",
|
|
634
|
+
flush=True,
|
|
635
|
+
)
|
|
636
|
+
# Do not wait for queued worker functions to start.
|
|
637
|
+
executor.shutdown(
|
|
638
|
+
wait=False,
|
|
639
|
+
cancel_futures=True,
|
|
640
|
+
)
|
|
641
|
+
raise
|
|
642
|
+
except Exception:
|
|
643
|
+
# One submission failed, so cancel the remaining batch.
|
|
644
|
+
cancel_requested.set()
|
|
645
|
+
with active_job_ids_lock:
|
|
646
|
+
jobs_to_cancel = list(active_job_ids)
|
|
647
|
+
for job_id in jobs_to_cancel:
|
|
648
|
+
try:
|
|
649
|
+
self.cancel_job(job_id)
|
|
650
|
+
except requests.RequestException as cancel_error:
|
|
651
|
+
print(
|
|
652
|
+
f"Could not cancel job {job_id}: {cancel_error}",
|
|
653
|
+
flush=True,
|
|
654
|
+
)
|
|
655
|
+
executor.shutdown(wait=False, cancel_futures=True)
|
|
656
|
+
raise
|
|
657
|
+
else:
|
|
658
|
+
executor.shutdown(wait=True)
|
|
659
|
+
# with ThreadPoolExecutor(
|
|
660
|
+
# max_workers=worker_count
|
|
661
|
+
# ) as executor:
|
|
662
|
+
# try:
|
|
663
|
+
# results = list(executor.map(submit_video, videos_iterable, final_video_paths_iterable, final_video_paths_external_view_iterable, final_video_paths_wrist_view_iterable,))
|
|
664
|
+
# except KeyboardInterrupt:
|
|
665
|
+
# for job_id in active_job_ids:
|
|
666
|
+
# try:
|
|
667
|
+
# self.cancel_job(job_id)
|
|
668
|
+
# except requests.RequestException:
|
|
669
|
+
# pass
|
|
670
|
+
# executor.shutdown(wait=False, cancel_futures=True)
|
|
671
|
+
# raise
|
|
672
|
+
if single_video:
|
|
673
|
+
return results[0]
|
|
674
|
+
else:
|
|
675
|
+
if len(results)==1:
|
|
676
|
+
return results[0]
|
|
677
|
+
else:
|
|
678
|
+
results_rewards = [result.rewards[0] for result in results]
|
|
679
|
+
# results_output_text = [result.output_text[0] for result in results if result.output_text is not None else None]
|
|
680
|
+
results_output_text = [result.output_text[0] if result.output_text is not None else None for result in results]
|
|
681
|
+
results_success_probs = [result.success_probs[0] if result.success_probs else None for result in results]
|
|
682
|
+
results_success = [result.success[0] if result.success else None for result in results]
|
|
683
|
+
results = SimpleNamespace(
|
|
684
|
+
model=model,
|
|
685
|
+
rewards=results_rewards,
|
|
686
|
+
output_text=results_output_text,
|
|
687
|
+
success_probs=results_success_probs,
|
|
688
|
+
success=results_success,
|
|
689
|
+
)
|
|
690
|
+
return results
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
|
|
402
694
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|