+ {% for id in cameras %}
+
+
Camera: {{ id }}
+
+
DISPLAY FPS: 0.0
+
+
TARGET FPS: 0.0
+
+
+
+

+
+
+ {% endfor %}
+
+
+
+
+
\ No newline at end of file
diff --git a/fastapi/tests/nginx.conf b/fastapi/tests/nginx.conf
new file mode 100644
index 0000000..ac1b556
--- /dev/null
+++ b/fastapi/tests/nginx.conf
@@ -0,0 +1,12 @@
+events { worker_connections 1024; }
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+ client_max_body_size 100M;
+
+ server {
+ listen 80;
+ server_name _;
+ }
+}
\ No newline at end of file
diff --git a/fastapi/tests/test_detections.py b/fastapi/tests/test_detections.py
new file mode 100644
index 0000000..6cac5b9
--- /dev/null
+++ b/fastapi/tests/test_detections.py
@@ -0,0 +1,2179 @@
+import argparse
+import csv
+import gc
+import logging
+import multiprocessing as mp
+import os
+import sys
+import threading
+import time
+from pathlib import Path
+
+import cv2
+import matplotlib.pyplot as plt
+import numpy as np
+import pytest
+import tensorrt as trt
+import torch
+import torch.nn.functional as F
+
+sys.path.insert(1, str(Path(__file__).parent.parent))
+from include.default_configs import (
+ CUSTOM_MODEL_FLAG_DEFAULT,
+ DEBUG_DEFAULT,
+ MODEL_NAME_DEFAULT,
+ THRESHOLD_VALUE,
+)
+from include.handlers import (
+ CPUStreamHandler,
+ DeviceBaseHandler,
+ GPUStreamHandler,
+ log_to_logger,
+)
+from include.handlers import test_rendering_worker as rendering_worker
+from include.utils import (
+ PipelineConfig,
+ tensor2opencv,
+)
+
+try:
+ torch.multiprocessing.set_start_method("spawn", force=True)
+except RuntimeError:
+ pass
+
+logging.getLogger("matplotlib").setLevel(logging.WARNING)
+logger = trt.Logger(trt.Logger.WARNING)
+trt.init_libnvinfer_plugins(logger, "")
+
+DEBUG_FLAG_DEFAULT = True if DEBUG_DEFAULT == "1" else False
+os.environ["OMP_NUM_THREADS"] = "1"
+os.environ["PYTORCH_ALLOC_CONF"] = "expandable_segments:True"
+force_export = False
+
+
+# def render_display_worker(queue, output_path, fps, size):
+# """Background process that draws labels and writes to video."""
+# fourcc = cv2.VideoWriter_fourcc(*"avc1")
+# writer = cv2.VideoWriter(output_path, fourcc, fps, size)
+
+# if not writer.isOpened():
+# print(f" [ERROR] VideoWriter failed to open: {output_path}")
+# return
+
+# while True:
+# item = queue.get()
+# if item is None:
+# break
+
+# display_frame, metadata_or_bbs, class_list = item
+# if (display_frame.shape[1], display_frame.shape[0]) != size:
+# display_frame = cv2.resize(display_frame, size)
+
+# if isinstance(metadata_or_bbs, dict):
+# for _, obj in metadata_or_bbs.items():
+# bbox = obj["bbox"]
+# x, y, w, h = bbox["x"], bbox["y"], bbox["width"], bbox["height"]
+# class_name = bbox["object"]
+# class_id = class_list.index(class_name) if class_name in class_list else 0
+# confidence = bbox["object_det"]["confidence"]
+
+# bb_color = get_detection_color(class_id, is_bgr=True)
+# label = f"{class_name} {confidence:.2f}"
+
+# cv2.rectangle(display_frame, (x, y), (x + w, y + h), bb_color, 2)
+# draw_label(display_frame, label, (x, y), color=bb_color, padding=5)
+# elif metadata_or_bbs is not None:
+# for box in metadata_or_bbs:
+# if torch.is_tensor(box):
+# box = box.tolist()
+# x1, y1, x2, y2 = map(int, box)
+# cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 0, 225), 2)
+
+# writer.write(display_frame)
+
+# writer.release()
+
+
+def fps_comparison_chart(chart_path, results, fps_key="Pipeline FPS (Video frames)"):
+ try:
+ names = [r["Test Name"] for r in results]
+ fps_values = [float(r[fps_key]) for r in results]
+
+ plt.figure(figsize=(10, 6))
+ plt.grid(axis="y", linestyle="--", alpha=0.7, zorder=0)
+
+ colors = ["#2ca02c" if "gpu" in n.lower() else "#1f77b4" for n in names]
+ bars = plt.bar(names, fps_values, color=colors, zorder=3)
+ plt.ylabel("Frames Per Second (FPS)")
+ plt.title(f"Performance Comparison: {chart_path.stem}")
+ plt.xticks(rotation=45)
+
+ for bar in bars:
+ yval = bar.get_height()
+ plt.text(
+ bar.get_x() + bar.get_width() / 2,
+ yval + 1,
+ f"{yval:.1f}",
+ ha="center",
+ va="bottom",
+ )
+
+ if fps_values:
+ plt.ylim(0, max(fps_values) * 1.2)
+
+ plt.tight_layout()
+ plt.savefig(str(chart_path))
+ print(f" Comparison chart saved to: {chart_path}")
+ except Exception:
+ print("Skipping chart generation: error occurred.")
+
+
+@pytest.fixture(scope="class")
+def setup_context(request):
+ """Replaces setUpClass: Runs once per test class."""
+ current_test_filename = Path(__file__).stem
+ test_dir = Path(__file__).parent
+ main_path = test_dir.parent
+ video_dir = main_path / "inputs"
+
+ VIDEO_FILENAME = os.getenv("VIDEO_FILENAME", "anduril_swarm_8K.mp4")
+ if video_dir.exists():
+ request.cls.video_path = video_dir / VIDEO_FILENAME
+ else:
+ video_dir = Path("/watch_dir")
+ request.cls.video_path = video_dir / VIDEO_FILENAME
+
+ model_name = os.getenv("MODEL_NAME", MODEL_NAME_DEFAULT)
+ request.cls.result_dir = (
+ test_dir
+ / f"{current_test_filename}_results/{model_name}"
+ / request.cls.video_path.stem
+ )
+ request.cls.result_dir.mkdir(parents=True, exist_ok=True)
+ request.cls.benchmarks = []
+ request.cls.csv_filename = (
+ f"pipeline_benchmarks_{model_name}_{request.cls.video_path.stem}.csv"
+ )
+
+ request.cls.name = request.cls.video_path.stem
+ request.cls.source = str(request.cls.video_path)
+ request.cls.active = True
+ request.cls.active_streams = {}
+
+ request.cls._shared_model = None
+ request.cls._shared_model_path = None
+ request.cls._shared_model_device = None
+ request.cls._shared_model_sf_enabled = None
+ yield
+
+ if request.cls.benchmarks:
+ # Filter and exclude rows that were interrupted or failed initialization due to an early pytest skip
+ request.cls.benchmarks = [
+ r for r in request.cls.benchmarks if r and "Test Name" in r
+ ]
+ results = request.cls.benchmarks
+ for row in results:
+ if "gpu" in row["Test Name"].lower():
+ match_name = row["Test Name"].replace("gpu", "cpu")
+ cpu_row = next(
+ (r for r in results if r["Test Name"] == match_name), None
+ )
+ if cpu_row:
+ gpu_fps = float(row["Pipeline FPS (Video frames)"])
+ cpu_fps = float(cpu_row["Pipeline FPS (Video frames)"])
+ speedup = (gpu_fps / cpu_fps) if cpu_fps > 0 else 0
+ row["Pipeline Speedup vs CPU"] = f"{speedup:.2f}x"
+ else:
+ row["Pipeline Speedup vs CPU"] = "N/A"
+ else:
+ row["Pipeline Speedup vs CPU"] = "Baseline (CPU)"
+
+ keys = results[0].keys()
+ with open(
+ str(request.cls.result_dir / request.cls.csv_filename), "w", newline=""
+ ) as f:
+ dict_writer = csv.DictWriter(f, fieldnames=keys)
+ dict_writer.writeheader()
+ dict_writer.writerows(results)
+
+ print(f"\n[FINAL] Benchmarks saved to {request.cls.csv_filename}")
+
+ for r in results:
+ print(
+ f" > {r['Test Name']}: {r['Pipeline FPS (Video frames)']} FPS | {r['Pipeline FPS (Target frames)']} FPS | {r['sf+roi+det FPS (Target frames)']} FPS | {r['Display FPS']} FPS | Speedup: {r.get('Pipeline Speedup vs CPU', 'N/A')}"
+ )
+
+ chart_path = (
+ request.cls.result_dir
+ / f"{request.cls.csv_filename.replace('.csv', '')}_sfFPS.png"
+ )
+ fps_comparison_chart(
+ chart_path, results, fps_key="sf+roi+det FPS (Video frames)"
+ )
+
+ chart_path = (
+ request.cls.result_dir
+ / f"{request.cls.csv_filename.replace('.csv', '')}_pipelineFPS.png"
+ )
+ fps_comparison_chart(chart_path, results, fps_key="Pipeline FPS (Video frames)")
+
+ chart_path = (
+ request.cls.result_dir
+ / f"{request.cls.csv_filename.replace('.csv', '')}_sfFPS_target.png"
+ )
+ fps_comparison_chart(
+ chart_path, results, fps_key="sf+roi+det FPS (Target frames)"
+ )
+
+ chart_path = (
+ request.cls.result_dir
+ / f"{request.cls.csv_filename.replace('.csv', '')}_pipelineFPS_target.png"
+ )
+ fps_comparison_chart(
+ chart_path, results, fps_key="Pipeline FPS (Target frames)"
+ )
+
+ chart_path = (
+ request.cls.result_dir
+ / f"{request.cls.csv_filename.replace('.csv', '')}_displayFPS_target.png"
+ )
+ fps_comparison_chart(chart_path, results, fps_key="Display FPS")
+
+
+@pytest.fixture(autouse=True)
+def each_test_setup(request):
+ test_class_self = request.instance
+ if torch.cuda.is_available():
+ torch.cuda.synchronize()
+
+ device = request.node.callspec.params.get("device")
+ detection_type = request.node.callspec.params.get("detection_type")
+ sf_enabled = request.node.callspec.params.get("sf_enabled")
+
+ test_class_self._testMethodName = (
+ f"sf_{detection_type}_{device}"
+ if sf_enabled
+ else f"yolo_{detection_type}_{device}"
+ )
+
+ # 1. Re-initialize a fresh configuration object
+ test_class_self.config = PipelineConfig(
+ CUSTOM_MODEL_FLAG=os.getenv("CUSTOM_MODEL_FLAG", CUSTOM_MODEL_FLAG_DEFAULT),
+ DEVICE=device.upper(),
+ OMIT_DETECTIONS_FLAG=True,
+ TEST_MODE=True,
+ DEBUG=os.getenv("DEBUG", DEBUG_DEFAULT),
+ DEBUG_FRAME_LIMIT=int(os.getenv("DEBUG_FRAME_LIMIT", 100)),
+ ENABLE_QUERYING=False,
+ MODEL_NAME=os.getenv("MODEL_NAME", MODEL_NAME_DEFAULT),
+ SMART_FILTERING_ENABLED=sf_enabled,
+ THRESHOLD_VALUE=int(os.getenv("THRESHOLD_VALUE", THRESHOLD_VALUE)),
+ DETECTION_TYPE=detection_type,
+ )
+
+ # video_output_name = f"{test_class_self._testMethodName}_detections_output.mp4"
+ vid_dir = test_class_self.result_dir / "results"
+ vid_dir.mkdir(parents=True, exist_ok=True)
+ # test_video_output_path = os.path.join(str(vid_dir), video_output_name)
+ os.environ["TEST_SUITE_RENDER_DIR"] = str(vid_dir)
+ test_class_self.config.SHARED_OUTPUT = str(test_class_self.result_dir)
+
+ # Reset core state machine properties before invoking any backend handlers
+ test_class_self.active = True
+ test_class_self._is_stopped = False
+
+ # FIXING ATTRIBUTEERRORS: Explicitly provision primitives before method re-binding
+ import threading
+ import time
+
+ test_class_self._stop_lock = threading.Lock()
+ # test_class_self.stat_start_time = time.perf_counter() # timing to display detection
+
+ test_class_self.device = test_class_self.config.DEVICE
+ test_class_self.device_input = test_class_self.config.device_input
+ test_class_self.resize_h, test_class_self.resize_w = [
+ test_class_self.config.MODEL_H,
+ test_class_self.config.MODEL_W,
+ ]
+
+ # Resolve concrete handler class type
+ HandlerClass = GPUStreamHandler if device == "gpu" else CPUStreamHandler
+ HandlerClass.pipeline_fn = test_class_self.__class__.pipeline_fn
+
+ # 2. Dynamically re-bind backend methods to this execution instance
+ import inspect
+ import types
+
+ handler_classes = [HandlerClass, DeviceBaseHandler]
+ all_method_names = set()
+
+ for cls in handler_classes:
+ for name, attr in inspect.getmembers(cls, predicate=inspect.isfunction):
+ all_method_names.add(name)
+
+ for method_name in all_method_names:
+ source_obj = (
+ HandlerClass if hasattr(HandlerClass, method_name) else DeviceBaseHandler
+ )
+ if hasattr(source_obj, method_name):
+ raw_func = getattr(source_obj, method_name)
+ if (
+ not hasattr(test_class_self.__class__, method_name)
+ or method_name == "pipeline_fn"
+ ):
+ setattr(
+ test_class_self,
+ method_name,
+ types.MethodType(raw_func, test_class_self),
+ )
+
+ # FIXING FILEEXISTSERRORS: Proactively clear lingering POSIX layout blocks in /dev/shm
+ from multiprocessing import shared_memory
+
+ for i in range(4): # Matches your self.ai_ring_depth footprint
+ stale_shm_name = f"shm_ai_640_{test_class_self.name}_{i}_{os.getpid()}"
+ try:
+ # Force link onto lingering handle and unlink it instantly from the OS map
+ lingering_shm = shared_memory.SharedMemory(name=stale_shm_name)
+ lingering_shm.close()
+ lingering_shm.unlink()
+ except FileNotFoundError:
+ pass
+
+ # 3. FORCE NATIVE PIPELINE PROVISIONING
+ test_class_self.setup_reader(
+ test_class_self.config.TARGET_FPS, test_class_self.config.CLIP_DURATION
+ )
+ test_class_self.initialize_variables()
+ test_class_self.setup_model(None)
+ test_class_self.prepare_pipeline()
+
+ # Reset runtime loop state properties
+ test_class_self.render_queue = None
+ test_class_self.frame_count_target = 0
+ test_class_self.next_process_idx = 0.0
+ test_class_self.frame_in_clip_count = 0
+ test_class_self.frame_count = 0
+ test_class_self.elapsed_display_time = 0.0
+
+ if hasattr(test_class_self, "setup_threads"):
+ test_class_self.setup_threads()
+
+ shared_event_buffer = {"sf": [], "roi": [], "det": []}
+ test_class_self.gpu_event_buffer = shared_event_buffer
+
+ if torch.cuda.is_available():
+ torch.cuda.empty_cache()
+ torch.cuda.ipc_collect()
+
+ yield
+
+ # Teardown logic
+ if (
+ hasattr(test_class_self, "render_queue")
+ and test_class_self.render_queue is not None
+ ):
+ try:
+ test_class_self.render_queue.put(None)
+ except Exception:
+ pass
+
+ render_proc_handle = getattr(test_class_self, "render_proc", None)
+ if render_proc_handle is not None and render_proc_handle._started.is_set():
+ test_class_self.render_proc.join(timeout=10.0)
+
+ # Signal the state machine to drop out of loops safely
+ test_class_self.active = False
+ if hasattr(test_class_self, "reader") and test_class_self.reader is not None:
+ try:
+ test_class_self.reader.stop()
+ except Exception:
+ pass
+
+ # Execute custom release blocks
+ test_class_self.stop()
+
+ # CRITICAL FIX: Block and wait for the background producer thread to die
+ # BEFORE we delete the reader attribute from the namespace map.
+ producer_thread_handle = getattr(test_class_self, "process_thread", None)
+ if producer_thread_handle is not None and producer_thread_handle.is_alive():
+ producer_thread_handle.join(timeout=5.0)
+
+ # Now it is structurally safe to purge attributes without causing cross-thread collisions
+ keys_to_purge = [
+ "reader",
+ "process_thread",
+ "inference_stream",
+ "bgs_stream",
+ "model",
+ "raw_input",
+ "ai_gpu_staging",
+ "ai_pinned_tensors",
+ "executor",
+ "io_executor",
+ ]
+ for key in keys_to_purge:
+ if hasattr(test_class_self, key):
+ delattr(test_class_self, key)
+
+ if (
+ hasattr(test_class_self, "gpu_event_buffer")
+ and test_class_self.gpu_event_buffer
+ ):
+ test_class_self.gpu_event_buffer.clear()
+ if hasattr(test_class_self, "component_stats") and test_class_self.component_stats:
+ test_class_self.component_stats.clear()
+
+ with torch.inference_mode():
+ if torch.cuda.is_available():
+ torch.cuda.synchronize()
+ torch.cuda.empty_cache()
+ torch.cuda.ipc_collect()
+
+ gc.collect()
+ time.sleep(0.2)
+
+
+@pytest.mark.usefixtures("setup_context")
+class TestSmartFilteringDetections:
+ # class TestSmartFilteringDetections(DeviceBaseHandler):
+ # """
+ # Unified testing harness.
+ # By inheriting from DeviceBaseHandler, 'self' functions as both
+ # the pytest telemetry harness and the live stream execution runner.
+ # """
+ # # Overriding __init__ to prevent standard initialization collisions with pytest
+ # def __init__(self, *args, **kwargs):
+ # pass
+ # SETUP --------------------------------------------
+
+ @pytest.mark.parametrize("device", ["cpu", "gpu"])
+ @pytest.mark.parametrize("sf_enabled", [True, False])
+ @pytest.mark.parametrize("detection_type", ["motion", "object"])
+ def test_detections(self, detection_type, device, sf_enabled):
+ """Unified test runner for all configurations."""
+ if detection_type == "motion" and not sf_enabled:
+ pytest.skip(
+ "Pure YOLO mode is structurally invalid for detection_type 'motion'."
+ )
+
+ # Run the actual model loader
+ # self.get_model_by_device(device, sf_enabled=self.config.sf_enabled)
+
+ # Execute
+ self.run_pipeline() # pipeline_fn)
+
+ def setup_threads(self):
+ # Shared 10MB memory for display
+ self.setup_shared_memory()
+
+ # Executor for Async YOLO tasks and FFmpeg re-encoding
+ # self.executor = ThreadPoolExecutor(max_workers=self.config.MAX_WORKERS)
+ # self.clip_executor = ThreadPoolExecutor(max_workers=self.config.MAX_WORKERS)
+
+ print(
+ f"sf_enabled: {self.config.sf_enabled}\tTEST_MODE: {self.config.TEST_MODE}",
+ flush=True,
+ )
+
+ # Producer: Handles acquisition and AI metadata logs
+ # self.process_thread = threading.Thread(
+ # target=self.run_pipeline,
+ # daemon=True,
+ # )
+
+ self.signal_queue = mp.Queue(maxsize=1)
+ self.render_queue = mp.Queue(maxsize=5)
+
+ # if self.config.TEST_MODE:
+ test_dir = os.getenv(
+ "TEST_SUITE_RENDER_DIR", str(Path(self.config.SHARED_OUTPUT))
+ )
+ os.makedirs(test_dir, exist_ok=True)
+
+ video_output_name = f"{self._testMethodName}_detections_output.mp4"
+ out_path = os.path.join(test_dir, video_output_name)
+
+ log_to_logger(
+ f"[TEST MODE] Detection results saved to: {out_path}", level="info"
+ )
+ self.render_proc = threading.Thread(
+ target=rendering_worker,
+ args=(
+ self.render_queue,
+ (self.disp_w, self.disp_h),
+ out_path,
+ self.target_fps,
+ ),
+ daemon=True,
+ )
+
+ # Dummy target alignment to prevent execution signature exceptions
+ self.display_proc = threading.Thread(target=lambda: None, daemon=True)
+ # else:
+ # self.render_proc = mp.Process(
+ # target=rendering_worker,
+ # args=(
+ # self.render_queue,
+ # self.shared_details,
+ # self.ready_buffer_idx,
+ # self.reader_active_idx,
+ # self.shm_frame_lengths,
+ # self.signal_queue,
+ # (self.disp_w, self.disp_h),
+ # self.config.DISPLAY_FRAME_QUALITY,
+ # ),
+ # )
+
+ # def display_signal_sync():
+ # while self.active:
+ # # Wait for signal
+ # # if self.mp_frame_ready_event.wait(timeout=1.0):
+ # # self.mp_frame_ready_event.clear()
+ # try:
+ # _ = self.signal_queue.get(timeout=1.0)
+ # # print(f"[DEBUG]: Signal received in FastAPI process for {self.name}", flush=True)
+ # # Wake FastAPI async loop in main thread
+ # self.loop.call_soon_threadsafe(self.frame_ready_event.set)
+ # except queue.Empty:
+ # continue
+
+ # self.display_proc = threading.Thread(
+ # target=display_signal_sync, daemon=True
+ # )
+
+ # if self.config.ENABLE_QUERYING:
+ # # NEW: Dedicated I/O pool for Disk/GPU transfers (Higher worker count for 8K)
+ # self.io_executor = ThreadPoolExecutor(max_workers=8)
+
+ # # Dedicated FFmpeg pool so re-encoding doesn't slow down live AI
+ # self.ffmpeg_executor = ThreadPoolExecutor(max_workers=2)
+
+ # if not self.config.TEST_MODE:
+ # # Sends metadata to VDMS
+ # self.metadata_thread = threading.Thread(
+ # target=send_metadata,
+ # args=(
+ # VDMSPool(self.config.DBHOST, self.config.DBPORT, size=10),
+ # self.config.DEBUG_FLAG,
+ # self.config.INGESTION,
+ # self.config.TEST_MODE,
+ # self.config.UDF_HOST,
+ # self.config.UDF_PORT,
+ # self.config.DBHOST,
+ # self.config.DBPORT,
+ # ),
+ # daemon=True,
+ # )
+
+ # # Consumer: Handles GPU-to-CPU download and Disk I/O (Writing resized frames to RAM disk)
+ # self.writer_thread = threading.Thread(
+ # target=self.video_writer_core_loop,
+ # args=(self.stop_writer,),
+ # daemon=True,
+ # )
+
+ # HELPERS --------------------------------------------
+ def _print_gpu_mem(self):
+ if torch.cuda.is_available():
+ # Memory currently used by tensors
+ allocated = torch.cuda.memory_allocated(0) / 1024**2
+ # Total memory reserved by PyTorch (the "Pool")
+ reserved = torch.cuda.memory_reserved(0) / 1024**2
+ print(f"\tAllocated: {allocated:0.2f} MB")
+ print(f"\tReserved: {reserved:0.2f} MB")
+ else:
+ print("\tCUDA not available.")
+
+ # def get_model_by_device(self, device, sf_enabled=False):
+ # """Singleton loader: only loads if device changes or model is missing."""
+ # if (
+ # sf_enabled
+ # and (self.frame_width * self.frame_height)
+ # <= self.config.SMART_FILTERING_PIXEL_CONSTRAINT
+ # ):
+ # sf_enabled = False
+
+ # if (
+ # TestSmartFilteringDetections._shared_model is not None
+ # and TestSmartFilteringDetections._shared_model_device == device
+ # and TestSmartFilteringDetections._shared_model_sf_enabled == sf_enabled
+ # ):
+ # self.model = TestSmartFilteringDetections._shared_model
+ # self.model_path = TestSmartFilteringDetections._shared_model_path
+ # return
+
+ # run_platform_name = "engine" if "cuda" in self.device_input else "openvino"
+
+ # if self.config.CUSTOM_MODEL_FLAG:
+ # dir_path = "/home/resources/models/ultralytics/custom_models"
+ # else:
+ # dir_path = f"/home/resources/models/ultralytics/{self.config.MODEL_NAME}/{self.config.MODEL_PRECISION}"
+
+ # (
+ # TestSmartFilteringDetections._shared_model,
+ # TestSmartFilteringDetections._shared_model_path,
+ # self.label_source,
+ # ) = get_model(
+ # # model_run_key, model_run_config, export=False
+ # Path(dir_path),
+ # self.config.MODEL_NAME,
+ # run_platform_name,
+ # self.device_input,
+ # batch=self.config.MODEL_MAX_BATCH_SIZE,
+ # force_export=force_export,
+ # sf_enabled=sf_enabled,
+ # model_h=self.resize_h,
+ # model_w=self.resize_w,
+ # )
+ # TestSmartFilteringDetections._shared_model_device = device
+ # TestSmartFilteringDetections._shared_model_sf_enabled = sf_enabled
+ # self.model = TestSmartFilteringDetections._shared_model
+ # # self.model.half()
+ # self.model_path = TestSmartFilteringDetections._shared_model_path
+
+ # W, H = self.resize_w, self.resize_h
+ # if not sf_enabled:
+ # W, H = self.frame_width, self.frame_height
+ # self.model_warmup(H, W)
+ # # self.pipeline_handler.model = self.model
+ # # self.pipeline_handler.model_warmup(H, W)
+
+ # def calculate_unique_coverage(self, merged_boxes, target_w=640, target_h=640):
+ # """
+ # FULLY VECTORIZED: Calculate pixel coverage without Python loops.
+ # Works for any number of boxes (1 to 1000+) with near-zero overhead.
+ # """
+ # if merged_boxes is None or merged_boxes.shape[0] == 0:
+ # return 0.0
+
+ # # 1. Scaling (Vectorized)
+ # # merged_boxes is [N, 4] -> [x1, y1, x2, y2] in 8K space
+ # scale = torch.tensor(
+ # [
+ # target_w / self.frame_width,
+ # target_h / self.frame_height,
+ # target_w / self.frame_width,
+ # target_h / self.frame_height,
+ # ],
+ # device=self.device_input,
+ # )
+
+ # # Scale and clamp all boxes at once on the GPU
+ # coords = (merged_boxes * scale).long()
+ # coords[:, [0, 2]] = coords[:, [0, 2]].clamp(0, target_w)
+ # coords[:, [1, 3]] = coords[:, [1, 3]].clamp(0, target_h)
+
+ # # 2. Vectorized Mask Filling
+ # # We create a 1D representation of the 640x640 mask for fast indexing
+ # mask = torch.zeros(
+ # target_h * target_w, device=self.device_input, dtype=torch.uint8
+ # )
+
+ # # For each box, we generate a range of indices and fill them.
+ # # Note: For small N (drones), a loop is okay, but for 'Swarm' noise,
+ # # we use this broadcasted approach:
+ # for i in range(coords.shape[0]):
+ # x1, y1, x2, y2 = coords[i]
+ # # Generate row indices for this box
+ # rows = torch.arange(y1, y2, device=self.device_input).view(-1, 1)
+ # # Calculate mask indices: (y * width) + x
+ # # This fills a horizontal slice of the mask in one GPU operation
+ # indices = (rows * target_w) + torch.arange(x1, x2, device=self.device_input)
+ # mask[indices] = 1
+
+ # # 3. Final Sum (GPU Reduction)
+ # return (torch.sum(mask).item() / (target_w * target_h)) * 100
+
+ def calculate_unique_coverage(self, merged_boxes, target_w=640, target_h=640):
+ """
+ TRUE LOOPLESS VECTORIZATION: Calculates combined bounding box pixel coverage
+ in a single GPU pass, completely avoiding CPU-GPU synchronization stalls.
+ """
+ if merged_boxes is None or merged_boxes.shape[0] == 0:
+ return 0.0
+
+ scale = torch.tensor(
+ [
+ target_w / self.frame_width,
+ target_h / self.frame_height,
+ target_w / self.frame_width,
+ target_h / self.frame_height,
+ ],
+ device=self.device_input,
+ )
+
+ coords = (merged_boxes * scale).long()
+ coords[:, [0, 2]] = coords[:, [0, 2]].clamp(0, target_w)
+ coords[:, [1, 3]] = coords[:, [1, 3]].clamp(0, target_h)
+
+ x1 = coords[:, 0].view(-1, 1, 1)
+ y1 = coords[:, 1].view(-1, 1, 1)
+ x2 = coords[:, 2].view(-1, 1, 1)
+ y2 = coords[:, 3].view(-1, 1, 1)
+
+ grid_y, grid_x = torch.meshgrid(
+ torch.arange(target_h, device=self.device_input),
+ torch.arange(target_w, device=self.device_input),
+ indexing="ij",
+ )
+
+ inside_boxes = (grid_x >= x1) & (grid_x < x2) & (grid_y >= y1) & (grid_y < y2)
+ unique_mask = torch.any(inside_boxes, dim=0)
+
+ return (torch.sum(unique_mask).item() / (target_w * target_h)) * 100
+
+ def _finalize_benchmarks(
+ self,
+ # n_frames,
+ num_objs,
+ total_pipeline_ms,
+ real_world_latency_ms,
+ coverage_percentages,
+ sf_enabled,
+ stat_frame_count,
+ stat_fps,
+ ):
+ """Aggregates metrics and adds them to the results list."""
+ latency_s = total_pipeline_ms / 1000.0 # Just pipeline (sf + roi_ det)
+ est_fps = self.frame_count / latency_s if latency_s > 0 else 0
+ est_fps_processed = self.frame_count_target / latency_s if latency_s > 0 else 0
+ duration_s = self.frame_count / self.input_fps if self.input_fps > 0 else 0
+
+ real_latency_s = real_world_latency_ms / 1000.0
+ real_est_fps = self.frame_count / real_latency_s if real_latency_s > 0 else 0
+ real_est_fps_processed = (
+ self.frame_count_target / real_latency_s if real_latency_s > 0 else 0
+ )
+
+ # Calculate averages for component breakdowns
+ avg_sf = (
+ sum(self.component_stats["sf"]) / len(self.component_stats["sf"])
+ if self.component_stats["sf"]
+ else 0
+ )
+ avg_roi = (
+ sum(self.component_stats["roi"]) / len(self.component_stats["roi"])
+ if self.component_stats["roi"]
+ else 0
+ )
+ avg_det = (
+ sum(self.component_stats["det"]) / len(self.component_stats["det"])
+ if self.component_stats["det"]
+ else 0
+ )
+ avg_cov = (
+ sum(coverage_percentages) / len(coverage_percentages)
+ if coverage_percentages
+ else (100.0 if not sf_enabled else 0)
+ )
+
+ avg_crops = (
+ sum(self.crops_per_frame_list) / len(self.crops_per_frame_list)
+ if self.crops_per_frame_list
+ else 0
+ )
+
+ # Total Latency Sum (SF + ROI + DET)
+ total_sum = avg_sf + avg_roi + avg_det
+
+ # Calculate how often we hit a high-motion cap (e.g., 20 crops)
+ capped_frames = sum(1 for c in self.crops_per_frame_list if c >= 20)
+ cap_rate = (
+ (capped_frames / len(self.crops_per_frame_list)) * 100
+ if self.crops_per_frame_list
+ else 0
+ )
+
+ self.__class__.benchmarks.append(
+ {
+ "Test Name": self._testMethodName,
+ "Detection Type": self.config.DETECTION_TYPE,
+ "Device": self.device,
+ "Smart Filtering": "Enabled" if sf_enabled else "Disabled",
+ "Video": self.video_path.name, # self.name?
+ "Video FPS": f"{self.input_fps:.2f}",
+ "Video Duration (s)": f"{duration_s:.4f}",
+ "Video Frames": self.frame_count,
+ "Target Frames": self.frame_count_target,
+ "Pipeline Latency (s)": f"{real_latency_s:.2f}",
+ "Display Latency (s)": f"{self.elapsed_display_time:.2f}",
+ "sf+roi+det Latency (s)": f"{latency_s:.2f}",
+ # Includes time by HW decoder (reader) and preparing output video
+ "Pipeline FPS (Video frames)": f"{real_est_fps:.2f}",
+ "Pipeline FPS (Target frames)": f"{real_est_fps_processed:.2f}",
+ # TIming to display frame (read to after send to render queue)
+ "Display Frames": stat_frame_count,
+ "Display FPS": f"{stat_fps:.2f}",
+ # Only sf + roi + det
+ "sf+roi+det FPS (Video frames)": f"{est_fps:.2f}",
+ "sf+roi+det FPS (Target frames)": f"{est_fps_processed:.2f}",
+ "Avg SF (ms)": f"{avg_sf:.2f}",
+ "Avg ROI (ms)": f"{avg_roi:.2f}",
+ "Avg Obj. Detection (ms)": f"{avg_det:.2f}",
+ "Total Breakdown Sum (ms)": f"{total_sum:.2f}",
+ "Avg Area Coverage %": f"{avg_cov:.2f}%",
+ "Avg Crops/Frame": f"{avg_crops:.1f}",
+ "Crop Cap Rate (>20)": f"{cap_rate:.1f}%",
+ "Objects Detected": num_objs,
+ }
+ )
+
+ print(f"\n[{self._testMethodName}] Latency: {latency_s:.2f} sec")
+ print(
+ f"\n[{self._testMethodName}] Pipeline FPS (Target frames): {real_est_fps_processed:.2f} ({self.frame_count_target} frames)"
+ )
+ print(
+ f"\n[{self._testMethodName}] sf+roi+det FPS (Target frames): {est_fps_processed:.2f} ({self.frame_count_target} frames)"
+ )
+ print(
+ f"\n[{self._testMethodName}] Display FPS (Target frames): {stat_fps:.2f} ({stat_frame_count} frames)"
+ )
+
+ # def tensor2opencv_gpu(self, frame_tensor):
+ # """
+ # GPU-native equivalent of tensor2opencv.
+ # Fixes the '3x3 ghosting' and 'ValueError' at 30+ FPS.
+ # """
+ # # 1. Handle Batch Dimension: (1, 3, 640, 640) -> (3, 640, 640)
+ # temp = frame_tensor.squeeze(0) if frame_tensor.ndim == 4 else frame_tensor
+
+ # # 2. Fix Layout & Shape: (3, 640, 640) -> (640, 640, 3)
+ # # contiguous() physically rearranges pixels in VRAM to interleave colors.
+ # # reshape() ensures we never see the (1, 409600, 3) shape again.
+ # gpu_hwc = temp.permute(1, 2, 0).reshape(640, 640, 3).contiguous()
+
+ # # 3. Visibility Fix: Scale floats (0.0-1.0) to bytes (0-255) on GPU
+ # if gpu_hwc.dtype != torch.uint8:
+ # gpu_hwc = (gpu_hwc * 255).clamp(0, 255).byte()
+
+ # # 4. Color Space Fix: RGB -> BGR (Matches OpenCV)
+ # gpu_bgr = gpu_hwc.flip(-1).contiguous()
+
+ # # 5. Bridge to CuPy (Zero-Copy)
+ # cp_frame = cp.from_dlpack(torch.utils.dlpack.to_dlpack(gpu_bgr))
+
+ # # 6. Select Double-Buffer from Pool
+ # target_buf_cpu = self.pinned_pool_np[self.pool_idx]
+ # target_buf_gpu = self.cp_pool[self.pool_idx]
+ # self.pool_idx = (self.pool_idx + 1) % 2
+
+ # if not hasattr(self, "transfer_stream"):
+ # self.transfer_stream = cp.cuda.Stream(non_blocking=True)
+
+ # with self.transfer_stream:
+ # # cp.copyto is more robust for pinned memory than target[:]
+ # cp.copyto(target_buf_gpu, cp_frame)
+ # # Download into the pinned CPU RAM
+ # target_buf_gpu.get(out=target_buf_cpu)
+
+ # # 7. Mandatory Sync: Wait for the DMA transfer to hit RAM
+ # self.transfer_stream.synchronize()
+
+ # # Return a snapshot copy so the worker has private memory for 30 FPS
+ # return target_buf_cpu.copy()
+
+ # DEBUG FUNCTIONS --------------------------------------------
+ def debug_save_mask(self, frame_source, frame_num, rois=None):
+ debug_dir = self.result_dir / "debug_mask" / self._testMethodName
+ debug_dir.mkdir(parents=True, exist_ok=True)
+
+ if frame_num > self.config.DEBUG_FRAME_LIMIT:
+ return
+
+ # Download or copy the data
+ if hasattr(frame_source, "download"):
+ img_cpu = frame_source.download()
+ elif torch.is_tensor(frame_source):
+ # .contiguous() fixes the horizontal "shredding"/static look
+ temp = frame_source.squeeze(0) if frame_source.ndim == 4 else frame_source
+ # img_cpu = temp.permute(1, 2, 0).contiguous().cpu().numpy()
+ if temp.ndim == 1:
+ temp = temp.view(self.resize_h, self.resize_w)
+
+ if temp.ndim == 3:
+ img_cpu = temp.permute(1, 2, 0).contiguous().cpu().numpy()
+ else:
+ img_cpu = temp.contiguous().cpu().numpy()
+ else:
+ # For numpy arrays (like your pinned memory), ensure memory is linear
+ img_cpu = np.ascontiguousarray(frame_source)
+
+ # Fix Visibility (Normalization)
+ # If float, scale to 0-255. If uint8, leave as is to avoid "neon" colors.
+ if img_cpu.dtype != np.uint8:
+ if img_cpu.max() <= 1.0:
+ img_cpu = (img_cpu * 255).clip(0, 255).astype(np.uint8)
+ else:
+ img_cpu = img_cpu.astype(np.uint8)
+
+ # Handle Color Space
+ # OpenCV imwrite expects BGR. If 3-channel (RGB), swap. If 1-channel, save as-is.
+ if len(img_cpu.shape) == 3:
+ # img_cpu = cv2.cvtColor(img_cpu, cv2.COLOR_RGB2BGR)
+ pass
+ else:
+ img_cpu = cv2.cvtColor(img_cpu, cv2.COLOR_GRAY2BGR)
+
+ # Draw 8K Boxes (Scaled down)
+ if rois is not None:
+ h_img, w_img = img_cpu.shape[:2]
+ scale_x = float(w_img) / self.frame_width
+ scale_y = float(h_img) / self.frame_height
+ boxes = rois.cpu().tolist() if torch.is_tensor(rois) else rois
+ for box in boxes:
+ x1, y1, x2, y2 = [
+ int(box[0] * scale_x),
+ int(box[1] * scale_y),
+ int(box[2] * scale_x),
+ int(box[3] * scale_y),
+ ]
+ cv2.rectangle(img_cpu, (x1, y1), (x2, y2), (0, 0, 255), 2)
+
+ # Save to disk
+ save_path = debug_dir / f"mask_{frame_num:04d}.jpg"
+ cv2.imwrite(str(save_path), img_cpu)
+
+ def debug_save_img_roi(self, frame_source, bbs_full_res, frame_num):
+ debug_dir = self.result_dir / "debug_analysis" / self._testMethodName
+ debug_dir.mkdir(parents=True, exist_ok=True)
+
+ if frame_num > self.config.DEBUG_FRAME_LIMIT:
+ return
+
+ # Download/Copy the frame
+ if torch.is_tensor(frame_source):
+ # .contiguous() is CRITICAL here to fix the "shredded" look
+ temp = frame_source.squeeze(0) if frame_source.ndim == 4 else frame_source
+ img_cpu = temp.permute(1, 2, 0).contiguous().cpu().numpy()
+ elif hasattr(frame_source, "download"):
+ img_cpu = frame_source.download()
+ else:
+ img_cpu = np.ascontiguousarray(frame_source)
+
+ # Fix Shape: restore spatial grid if flattened
+ if img_cpu.ndim == 3 and img_cpu.shape[0] == 1:
+ img_cpu = img_cpu.reshape((self.resize_h, self.resize_w, 3))
+
+ # Fix Visibility: ONLY multiply if it's actually floating point
+ # If uint8 is multiplied by 255, it wraps around and creates "neon" colors
+ if img_cpu.dtype != np.uint8:
+ if img_cpu.max() <= 1.0:
+ img_cpu = (img_cpu * 255).clip(0, 255).astype(np.uint8)
+ else:
+ img_cpu = img_cpu.astype(np.uint8)
+
+ # Color Space: Standardize to BGR for imwrite
+ if len(img_cpu.shape) != 3:
+ img_cpu = cv2.cvtColor(img_cpu, cv2.COLOR_GRAY2BGR)
+
+ # Draw 8K Boxes (Scaled down)
+ h_img, w_img = img_cpu.shape[:2]
+ scale_x = w_img / self.frame_width
+ scale_y = h_img / self.frame_height
+
+ if bbs_full_res is not None:
+ boxes = (
+ bbs_full_res.cpu().tolist()
+ if torch.is_tensor(bbs_full_res)
+ else bbs_full_res
+ )
+ for box in boxes:
+ x1, y1, x2, y2 = [
+ int(box[0] * scale_x),
+ int(box[1] * scale_y),
+ int(box[2] * scale_x),
+ int(box[3] * scale_y),
+ ]
+ cv2.rectangle(img_cpu, (x1, y1), (x2, y2), (0, 0, 255), 2)
+
+ cv2.imwrite(str(debug_dir / f"analysis_{frame_num:04d}.jpg"), img_cpu)
+
+ def debug_save_crops(self, cropped_batch, frame_num):
+ """Saves the first 5 crops of a batch to the results directory."""
+ debug_dir = self.result_dir / "debug_crops" / self._testMethodName
+ debug_dir.mkdir(parents=True, exist_ok=True)
+
+ # Only save for the first self.config.DEBUG_FRAME_LIMIT frames to avoid disk bloat
+ if frame_num > self.config.DEBUG_FRAME_LIMIT:
+ return
+
+ for i, crop in enumerate(cropped_batch[: self.config.DEBUG_FRAME_LIMIT]):
+ # Convert GPU Tensor [C, H, W] -> NumPy [H, W, C]
+ if torch.is_tensor(crop):
+ # Reverse normalization (* 255) and permute to BGR
+ img = (crop.squeeze(0).permute(1, 2, 0) * 255).byte().cpu().numpy()
+ else:
+ img = crop
+
+ cv2.imwrite(str(debug_dir / f"frame_{frame_num}_crop_{i}.jpg"), img)
+
+ def debug_save_img(self, frame_source, frame_num):
+ debug_dir = self.result_dir / "debug_test" / self._testMethodName
+ debug_dir.mkdir(parents=True, exist_ok=True)
+
+ if frame_num > self.config.DEBUG_FRAME_LIMIT:
+ return
+
+ # Download/Copy the frame
+ if torch.is_tensor(frame_source):
+ # .contiguous() is CRITICAL here to fix the "shredded" look
+ temp = frame_source.squeeze(0) if frame_source.ndim == 4 else frame_source
+ img_cpu = temp.permute(1, 2, 0).contiguous().cpu().numpy()
+ elif hasattr(frame_source, "download"):
+ img_cpu = frame_source.download()
+ else:
+ img_cpu = np.ascontiguousarray(frame_source)
+
+ # Fix Shape: restore spatial grid if flattened
+ if img_cpu.ndim == 3 and img_cpu.shape[0] == 1:
+ img_cpu = img_cpu.reshape((self.resize_h, self.resize_w, 3))
+
+ # Fix Visibility: ONLY multiply if it's actually floating point
+ # If uint8 is multiplied by 255, it wraps around and creates "neon" colors
+ if img_cpu.dtype != np.uint8:
+ if img_cpu.max() <= 1.0:
+ img_cpu = (img_cpu * 255).clip(0, 255).astype(np.uint8)
+ else:
+ img_cpu = img_cpu.astype(np.uint8)
+
+ # Color Space: Standardize to BGR for imwrite
+ if len(img_cpu.shape) == 3:
+ pass
+ else:
+ img_cpu = cv2.cvtColor(img_cpu, cv2.COLOR_GRAY2BGR)
+
+ cv2.imwrite(str(debug_dir / f"analysis_{frame_num:04d}.jpg"), img_cpu)
+
+ # # FRAME PROCESSORS --------------------------------------------
+ # def run_pipeline(self): #, pipeline_fn):
+ # # n_frames = 0
+ # num_objs = 0
+ # self.frame_count_target = 0
+ # self.next_process_idx = 0.0
+ # self.frame_in_clip_count = 0
+ # total_pipeline_time_ms = 0.0 # Track pure latency
+ # coverage_percentages = []
+ # self.component_stats = {"sf": [], "roi": [], "det": []}
+ # self.gpu_event_buffer = {"sf": [], "roi": [], "det": []}
+ # self.crops_per_frame_list = []
+
+ # # PRE-SYNC: Ensure GPU is idle before timing starts
+ # # if self.device_input == "cuda":
+ # # # Pre-allocate CUDA events for isolated GPU timing
+ # # # start_event = torch.cuda.Event(enable_timing=True)
+ # # # end_event = torch.cuda.Event(enable_timing=True)
+
+ # # if torch.cuda.is_available():
+ # # torch.cuda.synchronize()
+
+ # self.start()
+ # time.sleep(0.1)
+
+ # # start_time = time.perf_counter()
+ # total_session_start = time.perf_counter()
+
+ # while self.active: # and n_frames < 10*self.fps:
+ # # ret, frame = self.cap.read()
+ # device_frame, frame_num = self.reader.read()
+ # if device_frame is None:
+ # if self.reader.stopped:
+ # self.active = False
+ # break
+ # continue
+
+ # # n_frames += 1
+ # self.frame_count += 1
+ # is_target_frame = float(frame_num) >= self.next_process_idx
+ # nob = 0
+
+ # # if self.device_input == "cuda":
+ # # # start_eve nt.record()
+ # # nob, metrics = self.pipeline_fn(device_frame, frame_num, is_target_frame)
+ # # # end_event.record()
+ # # # torch.cuda.synchronize()
+ # # # total_pipeline_time_ms += start_event.elapsed_time(end_event)
+ # # else:
+ # # start_t = time.perf_counter()
+ # if self.device_input == "cuda":
+ # # Record the reader's current layout availability milestone
+ # curr_event = torch.cuda.Event()
+ # curr_event.record()
+
+ # # Instruct the isolated inference stream to wait for this specific frame context
+ # self.inference_stream.wait_event(curr_event)
+ # with torch.cuda.stream(self.inference_stream):
+ # nob, metrics = self.pipeline_fn(device_frame, frame_num, is_target_frame)
+ # # self.inference_stream.synchronize()
+
+ # else:
+ # nob, metrics = self.pipeline_fn(device_frame, frame_num, is_target_frame)
+ # # total_pipeline_time_ms += (time.perf_counter() - start_t) * 1000
+
+ # num_objs += nob
+
+ # if is_target_frame and metrics != {}:
+ # num_crops = len(metrics["bbs"]) if metrics["bbs"] is not None else 0
+ # self.crops_per_frame_list.append(num_crops)
+
+ # # self.component_stats["roi"].append(metrics["roi_time"])
+
+ # # if self.device_input != "cuda":
+ # if metrics.get("sf_time"):
+ # self.component_stats["sf"].append(metrics["sf_time"])
+ # if metrics.get("det_time"):
+ # self.component_stats["det"].append(metrics["det_time"])
+ # if metrics.get("roi_time"):
+ # self.component_stats["roi"].append(metrics["roi_time"])
+
+ # # Calculate coverage OUTSIDE the timed block to prevent interference
+ # if self.config.sf_enabled and metrics.get("bbs") is not None:
+ # cov = self.calculate_unique_coverage(metrics["bbs"])
+ # coverage_percentages.append(cov)
+
+ # # POST-SYNC: Ensure all GPU tasks finished before timing ends
+ # if self.device_input == "cuda":
+ # torch.cuda.synchronize()
+
+ # # ACTUAL speed including all overhead
+ # real_world_latency_ms = (time.perf_counter() - total_session_start) * 1000
+
+ # # if self.device_input == "cuda":
+ # # # Move GPU event timings into component_stats lists
+ # # for key in ["sf", "det", "roi"]:
+ # # for start, end in self.gpu_event_buffer[key]:
+ # # self.component_stats[key].append(start.elapsed_time(end))
+
+ # # Calculate Pure processing latency
+ # # This excludes: Reader, Tensor2OpenCV, Queueing, and Video Writing
+ # total_pipeline_time_ms = (
+ # sum(self.component_stats["sf"])
+ # + sum(self.component_stats["roi"])
+ # + sum(self.component_stats["det"])
+ # )
+
+ # # assert num_objs > 0
+ # if num_objs == 0:
+ # print(f" [WARNING] No objects detected for {self._testMethodName}")
+
+ # self._finalize_benchmarks(
+ # self.frame_count,
+ # num_objs,
+ # total_pipeline_time_ms,
+ # real_world_latency_ms,
+ # coverage_percentages,
+ # self.config.sf_enabled,
+ # self.stat_frame_count,
+ # self.stat_fps,
+ # )
+
+ def run_pipeline(self):
+ num_objs = 0
+ self.frame_count_target = 0
+ self.next_process_idx = 0.0
+ self.frame_in_clip_count = 0
+ total_pipeline_time_ms = 0.0
+ coverage_percentages = []
+ self.component_stats = {"sf": [], "roi": [], "det": []}
+ self.crops_per_frame_list = []
+
+ self.step_size = (
+ float(self.input_fps) / float(self.target_fps)
+ if hasattr(self, "target_fps")
+ else 1.0
+ )
+
+ self.start()
+ time.sleep(0.1)
+
+ total_session_start = time.perf_counter()
+
+ while self.active:
+ # 1. CAPTURE COMPLETE CYCLE OVERHEAD
+ # t_cycle_start = time.perf_counter()
+
+ device_frame, frame_num = self.reader.read()
+ if device_frame is None:
+ if self.reader is None or (
+ hasattr(self.reader, "stopped") and self.reader.stopped
+ ):
+ # --- CRITICAL PIPELINE DRAIN START ---
+ print(
+ "[INFO] Reader reached EOF. Draining asynchronous workers and VRAM queues..."
+ )
+
+ # 1. Thread Pool Flush: Force the CPU thread to block here until
+ # every single background pipeline task in the executor finishes.
+ if hasattr(self, "executor") and self.executor:
+ self.executor.shutdown(wait=True)
+
+ # 2. Render Queue Flush: If you utilize an asynchronous video frame
+ # saving worker thread, wait for its queue tasks to bottom out.
+ if hasattr(self, "render_queue") and self.render_queue:
+ while not self.render_queue.empty():
+ time.sleep(0.01)
+
+ # 3. GPU Hardware Flush: Force the GPU to completely finish
+ # all remaining background subtraction, crops, and YOLO operations.
+ if self.device_input == "cuda":
+ torch.cuda.synchronize()
+
+ self.active = False
+ break
+ continue
+
+ self.stat_start_time = time.perf_counter() # timing to display detection
+ self.frame_count += 1
+ # is_target_frame = float(frame_num) >= self.next_process_idx
+ # CRITICAL CADENCE DRIFT FIX: If input FPS equals target FPS, or if Smart Filtering
+ # is disabled (YOLO baseline), force is_target_frame to ALWAYS be True.
+ # This completely bypasses floating-point accumulation drift errors.
+ if (
+ not self.config.sf_enabled
+ or abs(float(self.input_fps) - float(self.target_fps)) < 0.01
+ ):
+ is_target_frame = True
+ else:
+ is_target_frame = float(frame_num) >= self.next_process_idx
+
+ # 2. DISPATCH WORKLOADS ASYNCHRONOUSLY
+ metrics = {}
+ if is_target_frame:
+ self.next_process_idx += self.step_size
+
+ if self.device_input == "cuda":
+ # Instantiate a lightweight hardware fence event object
+ curr_event = torch.cuda.Event(enable_timing=False)
+ # curr_event.record()
+ # Record the exact milestone on the default stream right after fetching the frame data
+ curr_event.record(torch.cuda.default_stream())
+ self.inference_stream.wait_event(curr_event)
+ with torch.cuda.stream(self.inference_stream):
+ # 1. Isolate the incoming image buffer canvas
+ isolated_device_frame = (
+ device_frame.clone()
+ if torch.is_tensor(device_frame)
+ else device_frame.copy()
+ )
+
+ # 2. RUN BACKGROUND SUBTRACTION IMMEDIATELY ON THE PRODUCER TIMELINE
+ # This guarantees that the mask matches this exact frame_num before threads overlap!
+ nob, metrics = self.pipeline_fn(
+ isolated_device_frame,
+ frame_num,
+ is_target_frame,
+ self.stat_start_time,
+ )
+ else:
+ # 1. Isolate the incoming image buffer canvas
+ isolated_device_frame = (
+ device_frame.clone()
+ if torch.is_tensor(device_frame)
+ else device_frame.copy()
+ )
+
+ # 2. RUN BACKGROUND SUBTRACTION IMMEDIATELY ON THE PRODUCER TIMELINE
+ # This guarantees that the mask matches this exact frame_num b
+ nob, metrics = self.pipeline_fn(
+ isolated_device_frame,
+ frame_num,
+ is_target_frame,
+ self.stat_start_time,
+ )
+
+ num_objs += nob
+ # else:
+ # # Process background execution context for skipped frames
+ # nob, metrics = self.pipeline_fn(
+ # device_frame, frame_num, is_target_frame, self.stat_start_time
+ # )
+
+ # 3. ENFORCE UNIFIED HARDWARE TIMING BARRIER
+ # if self.device_input == "cuda":
+ # torch.cuda.synchronize()
+
+ # t_cycle_end = time.perf_counter()
+ # cycle_total_ms = (t_cycle_end - t_cycle_start) * 1000.0
+
+ # 4. ALLOCATE ALL TRACKING TIMINGS ACCURATELY
+ # if is_target_frame:
+ if metrics != {}:
+ num_crops = len(metrics["bbs"]) if metrics["bbs"] is not None else 0
+ self.crops_per_frame_list.append(num_crops)
+
+ if self.config.sf_enabled:
+ self.component_stats["sf"].append(metrics["sf_time"])
+ self.component_stats["roi"].append(metrics["roi_time"])
+ else:
+ # Full-frame YOLO baseline accounts for total data movement cycle
+ # self.component_stats["det"].append(cycle_total_ms)
+ self.component_stats["sf"].append(0.0)
+ self.component_stats["roi"].append(0.0)
+
+ self.component_stats["det"].append(metrics["det_time"])
+
+ if self.config.sf_enabled and metrics.get("bbs") is not None:
+ cov = self.calculate_unique_coverage(metrics["bbs"])
+ coverage_percentages.append(cov)
+ # else:
+ # # CRITICAL METRICS FIX: If Smart Filtering runs on a skipped frame,
+ # # its mask generation overhead MUST be captured and tracked!
+ # if self.config.sf_enabled and metrics != {}:
+ # self.component_stats["sf"].append(metrics["sf_time"])
+
+ if self.device_input == "cuda":
+ torch.cuda.synchronize()
+
+ real_world_latency_ms = (time.perf_counter() - total_session_start) * 1000.0
+
+ total_pipeline_time_ms = (
+ sum(self.component_stats["sf"])
+ + sum(self.component_stats["roi"])
+ + sum(self.component_stats["det"])
+ )
+
+ if num_objs == 0:
+ print(f" [WARNING] No objects detected for {self._testMethodName}")
+
+ self._finalize_benchmarks(
+ # self.frame_count,
+ num_objs,
+ total_pipeline_time_ms,
+ real_world_latency_ms,
+ coverage_percentages,
+ self.config.sf_enabled,
+ self.stat_frame_count,
+ self.stat_fps,
+ )
+
+ # # TESTS --------------------------------------------
+ # def pipeline_fn(self, device_frame, overall_frame_num, is_target_frame):
+ # num_objs = 0
+ # metrics = {"sf_time": 0, "roi_time": 0, "det_time": 0, "bbs": None}
+
+ # # Initialize timing event handle pairs
+ # sf_start, sf_end = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
+ # roi_start, roi_end = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
+ # det_start, det_end = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
+
+ # # --- 1. MOTION MASK GENERATION GATE ---
+ # if self.config.sf_enabled:
+ # if self.device_input == "cuda":
+ # sf_start.record(self.inference_stream)
+ # inf_data = self.rbtd_full_gpu(device_frame)
+ # sf_end.record(self.inference_stream)
+ # else:
+ # t_start = time.perf_counter()
+ # inf_data = self.rbtd_full_cpu(device_frame)
+ # metrics["sf_time"] = (time.perf_counter() - t_start) * 1000.0
+ # else:
+ # inf_data = {}
+
+ # # --- PIPELINE AT TARGET RATE ---
+ # if not is_target_frame:
+ # # If skipping, pull outstanding execution records immediately
+ # if self.device_input == "cuda" and self.config.sf_enabled:
+ # self.inference_stream.synchronize()
+ # metrics["sf_time"] = sf_start.elapsed_time(sf_end)
+ # return num_objs, metrics
+
+ # self.next_process_idx += self.step_size
+ # self.frame_count_target += 1
+ # self.frame_in_clip_count += 1
+ # inf_data["frameNum"] = self.frame_count_target
+
+ # # --- 2. FULL-RESOLUTION ROI EXTRACTION MAPS ---
+ # bbs_full_res = None
+ # if self.config.sf_enabled:
+ # if self.device_input == "cuda":
+ # roi_start.record(self.inference_stream)
+ # bbs_full_res = self.get_gpu_rois(
+ # inf_data["full_frame"],
+ # self.frame_count_target,
+ # inf_data["mask"],
+ # )
+ # roi_end.record(self.inference_stream)
+ # metrics["bbs"] = bbs_full_res
+ # else:
+ # t_start = time.perf_counter()
+ # bbs_full_res = self.get_cpu_rois(
+ # inf_data["full_frame"],
+ # self.frame_count_target,
+ # inf_data["mask"],
+ # )
+ # metrics["roi_time"] = (time.perf_counter() - t_start) * 1000.0
+ # metrics["bbs"] = bbs_full_res
+
+ # if self.config.DEBUG_FLAG:
+ # if self.device_input == "cuda":
+ # torch.cuda.stream(self.inference_stream)
+ # torch.cuda.synchronize()
+ # display_source = inf_data["mask"]
+ # self.debug_save_mask(
+ # display_source, self.frame_count_target, rois=bbs_full_res
+ # )
+
+ # clean_bbs = []
+ # if self.config.sf_enabled and bbs_full_res is not None:
+ # if torch.is_tensor(bbs_full_res):
+ # clean_bbs = bbs_full_res.detach().cpu().numpy()
+ # else:
+ # clean_bbs = np.array(bbs_full_res)
+
+ # # --- 3. MODEL INFERENCE TIMING BLOCK ---
+ # if self.device_input == "cuda":
+ # det_start.record(self.inference_stream)
+ # else:
+ # t_start = time.perf_counter()
+
+ # if self.config.DETECTION_TYPE != "motion":
+ # det_frame = inf_data["full_frame"] if "full_frame" in inf_data else device_frame
+ # merged = clean_bbs if self.config.sf_enabled else None
+ # metadata, _ = self.get_detections(
+ # det_frame,
+ # self.frame_in_clip_count,
+ # merged=merged,
+ # thickness=self.config.THICKNESS,
+ # device_input=self.config.device_input,
+ # )
+ # num_objs = len(metadata.keys())
+ # else:
+ # num_objs = len(clean_bbs)
+ # metadata = clean_bbs
+
+ # if self.device_input == "cuda":
+ # det_end.record(self.inference_stream)
+
+ # # CRITICAL PERFORMANCE SYNCHRONIZATION POINT:
+ # # We execute a single stream synchronization barrier here at the end of the entire loop.
+ # # This allows the GPU kernels to run overlapped and fully concurrently!
+ # self.inference_stream.synchronize()
+
+ # # Unpack hardware timings efficiently
+ # if self.config.sf_enabled:
+ # metrics["sf_time"] = sf_start.elapsed_time(sf_end)
+ # metrics["roi_time"] = roi_start.elapsed_time(roi_end)
+ # metrics["det_time"] = det_start.elapsed_time(det_end)
+ # metrics["bbs"] = bbs_full_res
+ # else:
+ # metrics["det_time"] = (time.perf_counter() - t_start) * 1000.0
+
+ # # --- 4. RENDER WORKER PREPARATION ---
+ # display_source = inf_data["full_frame"] if (inf_data and "full_frame" in inf_data) else device_frame
+
+ # if self.device_input == "cuda":
+ # gpu_resized = F.interpolate(
+ # display_source.unsqueeze(0).float(),
+ # size=(self.disp_h, self.disp_w),
+ # mode="bilinear",
+ # align_corners=False,
+ # ).squeeze(0).contiguous()
+ # disp_frame = np.copy(tensor2opencv(gpu_resized, self.config.device_input, is_bgr=True))
+ # else:
+ # cpu_resized = cv2.resize(device_frame, (self.disp_w, self.disp_h))
+ # disp_frame = np.copy(tensor2opencv(cpu_resized, self.config.device_input, is_bgr=True))
+
+ # data_to_draw = clean_bbs if self.config.DETECTION_TYPE == "motion" else metadata
+
+ # try:
+ # if hasattr(self, "render_queue") and getattr(self, "render_queue", None) is not None and not self.render_queue.full():
+ # self.render_queue.put(
+ # (
+ # disp_frame,
+ # inf_data["frameNum"] if "frameNum" in inf_data else self.frame_count_target,
+ # data_to_draw,
+ # self.label_source,
+ # )
+ # )
+ # if self.config.DEBUG_FLAG:
+ # self.debug_save_img(disp_frame, self.frame_count_target)
+ # self.debug_save_img_roi(disp_frame, bbs_full_res, self.frame_count_target)
+ # except queue.Full:
+ # pass
+
+ # self.update_frame()
+ # return num_objs, metrics
+
+ def pipeline_fn(
+ self, device_frame, overall_frame_num, is_target_frame, stat_start_time
+ ):
+ num_objs = 0
+ metrics = {"sf_time": 0, "roi_time": 0, "det_time": 0, "bbs": None}
+
+ # Pre-allocate non-blocking event records to extract clean GPU timings
+ sf_start, sf_end = (
+ torch.cuda.Event(enable_timing=True),
+ torch.cuda.Event(enable_timing=True),
+ )
+ roi_start, roi_end = (
+ torch.cuda.Event(enable_timing=True),
+ torch.cuda.Event(enable_timing=True),
+ )
+ det_start, det_end = (
+ torch.cuda.Event(enable_timing=True),
+ torch.cuda.Event(enable_timing=True),
+ )
+
+ # --- 1. MOTION MASK GENERATION GATE ---
+ if self.config.sf_enabled:
+ if self.device_input == "cuda":
+ sf_start.record(self.inference_stream)
+ inf_data = self.rbtd_full_gpu(device_frame)
+ sf_end.record(self.inference_stream)
+ else:
+ t_start = time.perf_counter()
+ inf_data = self.rbtd_full_cpu(device_frame)
+ metrics["sf_time"] = (time.perf_counter() - t_start) * 1000.0
+ else:
+ inf_data = {}
+
+ # --- PIPELINE AT TARGET RATE ---
+ if not is_target_frame:
+ # Safe, non-blocking timing extraction for skipped frames
+ if self.device_input == "cuda" and self.config.sf_enabled:
+ torch.cuda.synchronize()
+ metrics["sf_time"] = sf_start.elapsed_time(sf_end)
+ return num_objs, metrics
+
+ self.frame_count_target += 1
+ self.frame_in_clip_count += 1
+ inf_data["frameNum"] = self.frame_count_target
+
+ # --- 2. FULL-RESOLUTION ROI EXTRACTION MAPS ---
+ bbs_full_res = None
+ if self.config.sf_enabled:
+ if self.device_input == "cuda":
+ roi_start.record(self.inference_stream)
+ bbs_full_res = self.get_gpu_rois(
+ inf_data["full_frame"],
+ self.frame_count_target,
+ inf_data["mask"],
+ )
+ roi_end.record(self.inference_stream)
+ metrics["bbs"] = bbs_full_res
+ else:
+ t_start = time.perf_counter()
+ bbs_full_res = self.get_cpu_rois(
+ inf_data["full_frame"],
+ self.frame_count_target,
+ inf_data["mask"],
+ )
+ metrics["roi_time"] = (time.perf_counter() - t_start) * 1000.0
+ metrics["bbs"] = bbs_full_res
+
+ if self.config.DEBUG_FLAG:
+ # if self.device_input == "cuda":
+ # # Isolate data capturing using an asynchronous memory clone operation
+ # # This safely copies data without dropping your multi-stream execution pipeline concurrency
+ # display_source = inf_data["mask"].clone().to("cpu", non_blocking=True)
+ # else:
+ display_source = inf_data["mask"]
+ # self.inference_stream.synchronize()
+ # display_source = inf_data["mask"]
+ self.debug_save_mask(
+ display_source, self.frame_count_target, rois=bbs_full_res
+ )
+
+ clean_bbs = []
+ if self.config.sf_enabled and bbs_full_res is not None:
+ if torch.is_tensor(bbs_full_res):
+ clean_bbs = bbs_full_res.detach().cpu().numpy()
+ else:
+ clean_bbs = np.array(bbs_full_res)
+
+ # --- 3. MODEL INFERENCE TIMING BLOCK ---
+ if self.device_input == "cuda":
+ det_start.record(self.inference_stream)
+ else:
+ t_start = time.perf_counter()
+
+ if self.config.DETECTION_TYPE != "motion":
+ # det_frame = (
+ # inf_data["full_frame"] if "full_frame" in inf_data else device_frame
+ # )
+ # Isolate your image buffer array view to prevent upstream reader pointer races
+ if "full_frame" in inf_data:
+ det_frame = inf_data["full_frame"]
+ else:
+ det_frame = (
+ device_frame.clone()
+ if torch.is_tensor(device_frame)
+ else device_frame.copy()
+ )
+
+ merged = clean_bbs if self.config.sf_enabled else None
+ metadata, _ = self.get_detections(
+ det_frame,
+ self.frame_in_clip_count,
+ merged=merged,
+ thickness=self.config.THICKNESS,
+ device_input=self.config.device_input,
+ )
+ num_objs = len(metadata.keys())
+ else:
+ num_objs = len(clean_bbs)
+ metadata = clean_bbs
+
+ if self.device_input == "cuda":
+ det_end.record(self.inference_stream)
+
+ torch.cuda.synchronize()
+
+ # Extract hardware timings smoothly without stalling mid-run
+ if self.config.sf_enabled:
+ metrics["sf_time"] = sf_start.elapsed_time(sf_end)
+ metrics["roi_time"] = roi_start.elapsed_time(roi_end)
+ metrics["det_time"] = det_start.elapsed_time(det_end)
+ metrics["bbs"] = bbs_full_res
+ else:
+ metrics["det_time"] = (time.perf_counter() - t_start) * 1000.0
+
+ # --- 4. RENDER WORKER PREPARATION ---
+ display_source = (
+ inf_data["full_frame"]
+ if (inf_data and "full_frame" in inf_data)
+ else device_frame
+ )
+
+ if self.device_input == "cuda":
+ gpu_resized = (
+ F.interpolate(
+ display_source.unsqueeze(0).float(),
+ size=(self.disp_h, self.disp_w),
+ mode="bilinear",
+ align_corners=False,
+ )
+ .squeeze(0)
+ .contiguous()
+ )
+ disp_frame = np.copy(
+ tensor2opencv(gpu_resized, self.config.device_input, is_bgr=True)
+ )
+ else:
+ cpu_resized = cv2.resize(device_frame, (self.disp_w, self.disp_h))
+ disp_frame = np.copy(
+ tensor2opencv(cpu_resized, self.config.device_input, is_bgr=True)
+ )
+
+ data_to_draw = clean_bbs if self.config.DETECTION_TYPE == "motion" else metadata
+
+ # try:
+ # if (
+ # hasattr(self, "render_queue")
+ # and getattr(self, "render_queue", None) is not None
+ # and not self.render_queue.full()
+ # ):
+ # self.render_queue.put(
+ # (
+ # disp_frame,
+ # inf_data["frameNum"]
+ # if "frameNum" in inf_data
+ # else self.frame_count_target,
+ # data_to_draw,
+ # self.label_source,
+ # )
+ # )
+ # if self.config.DEBUG_FLAG:
+ # self.debug_save_img(disp_frame, self.frame_count_target)
+ # self.debug_save_img_roi(
+ # disp_frame, bbs_full_res, self.frame_count_target
+ # )
+ # except queue.Full:
+ # pass
+
+ if (
+ hasattr(self, "render_queue")
+ and getattr(self, "render_queue", None) is not None
+ ):
+ self.render_queue.put(
+ (
+ disp_frame,
+ inf_data["frameNum"]
+ if "frameNum" in inf_data
+ else self.frame_count_target,
+ data_to_draw,
+ self.label_source,
+ )
+ )
+ if self.config.DEBUG_FLAG:
+ self.debug_save_img(disp_frame, self.frame_count_target)
+ self.debug_save_img_roi(disp_frame, bbs_full_res, self.frame_count_target)
+
+ self.update_frame(stat_start_time)
+ return num_objs, metrics
+
+ # def sf_cpu_pipeline_fn(self, device_frame, overall_frame_num, is_target_frame):
+ # num_objs = 0
+ # metrics = {"sf_time": 0, "roi_time": 0, "det_time": 0, "bbs": None}
+
+ # # Smart Filtering (Resize / Background Subtraction / Threshold / Dilate)
+ # t_start = time.perf_counter()
+ # inf_data = self.rbtd_full_cpu(device_frame)
+ # metrics["sf_time"] = (time.perf_counter() - t_start) * 1000
+
+ # # Only keep frames for TARGET_FPS
+ # # Videos are written for target frames only
+ # if is_target_frame:
+ # self.next_process_idx += self.step_size
+ # self.frame_count_target += 1 # 1-indexed
+
+ # if not inf_data or inf_data["mask"] is None:
+ # return num_objs, metrics
+
+ # metadata = {}
+ # bbs_to_send = []
+ # data_to_draw = []
+ # if inf_data:
+ # inf_data["frameNum"] = self.frame_count_target
+
+ # # Get ROIs
+ # t_start = time.perf_counter()
+ # bbs_full_res = self.get_cpu_rois(
+ # inf_data["full_frame"],
+ # self.frame_count_target,
+ # inf_data["mask"],
+ # )
+ # metrics["roi_time"] = (time.perf_counter() - t_start) * 1000
+ # metrics["bbs"] = bbs_full_res
+
+ # if self.config.DEBUG_FLAG:
+ # display_source = inf_data["mask"]
+ # self.debug_save_mask(
+ # display_source, self.frame_count_target, rois=bbs_full_res
+ # )
+
+ # if bbs_full_res is not None and len(bbs_full_res) == 0:
+ # return num_objs, metrics
+
+ # # if self.config.DEBUG_FLAG or self.config.DETECTION_TYPE == "motion":
+ # # display_source = (
+ # # inf_data["full_frame"]
+ # # if (inf_data and "full_frame" in inf_data)
+ # # else device_frame
+ # # )
+ # # cpu_resized = cv2.resize(
+ # # display_source,
+ # # (self.resize_w, self.resize_h),
+ # # interpolation=cv2.INTER_NEAREST,
+ # # )
+
+ # t_start = time.perf_counter()
+ # if self.config.DETECTION_TYPE == "motion":
+ # # Motion Mode: Prepare boxes for drawing
+ # if (
+ # bbs_full_res is not None
+ # and bbs_full_res.ndim == 2
+ # and bbs_full_res.size(0) > 0
+ # ):
+ # scaled_resized_bbs = bbs_full_res / self.scales_tensor
+ # bbs_to_send = scaled_resized_bbs.cpu().tolist()
+ # else:
+ # bbs_to_send = []
+
+ # data_to_draw = bbs_to_send
+ # num_objs = len(bbs_full_res)
+ # else:
+ # # Object Mode: Run YOLO and prepare metadata
+ # det_frame = inf_data["full_frame"] if inf_data else device_frame
+ # metadata, _ = self.get_detections(
+ # det_frame,
+ # self.frame_count_target, # Frame used in metadata
+ # merged=bbs_full_res,
+ # thickness=self.config.THICKNESS,
+ # device_input=self.config.device_input,
+ # )
+ # data_to_draw = metadata
+ # num_objs = len(metadata.keys())
+
+ # metrics["det_time"] = (time.perf_counter() - t_start) * 1000
+
+ # # --- OFFLOAD TO QUEUE (Run for EVERY target frame) ---
+ # if not self.config.TEST_MODE:
+ # display_source = (
+ # inf_data["full_frame"]
+ # if (inf_data and "full_frame" in inf_data)
+ # else device_frame
+ # )
+ # cpu_resized = cv2.resize(display_source, (self.resize_w, self.resize_h))
+ # display_frame = tensor2opencv(
+ # cpu_resized, self.config.device_input, is_bgr=True
+ # )
+ # self.render_queue.put((display_frame, data_to_draw, self.label_source))
+
+ # if self.config.DEBUG_FLAG:
+ # cpu_resized = cv2.resize(
+ # display_source, (self.resize_w, self.resize_h)
+ # )
+ # self.debug_save_img(cpu_resized, self.frame_count_target)
+ # self.debug_save_img_roi(
+ # cpu_resized, bbs_full_res, self.frame_count_target
+ # )
+
+ # return num_objs, metrics
+
+ # def sf_gpu_pipeline_fn(self, device_frame, overall_frame_num, is_target_frame):
+ # num_objs = 0
+ # metrics = {"sf_time": 0, "roi_time": 0, "det_time": 0, "bbs": None}
+
+ # # Smart Filtering (Resize / Background Subtraction / Threshold / Dilate)
+ # sf_start, sf_end = (
+ # torch.cuda.Event(enable_timing=True),
+ # torch.cuda.Event(enable_timing=True),
+ # )
+ # sf_start.record()
+ # inf_data = self.rbtd_full_gpu(device_frame)
+ # sf_end.record()
+ # self.gpu_event_buffer["sf"].append((sf_start, sf_end))
+
+ # # Only keep frames for TARGET_FPS
+ # # Videos are written for target frames only
+ # if is_target_frame:
+ # self.next_process_idx += self.step_size
+ # self.frame_count_target += 1 # 1-indexed
+
+ # if not inf_data or inf_data["mask"] is None:
+ # return num_objs, metrics
+
+ # metadata = {}
+ # bbs_to_send = []
+ # data_to_draw = []
+ # if inf_data:
+ # inf_data["frameNum"] = self.frame_count_target
+
+ # # Get ROIs
+ # # time.perf_counter accurate since called self.bgs_stream.waitForCompletion()
+ # # t_start = time.perf_counter()
+ # roi_start = torch.cuda.Event(enable_timing=True)
+ # roi_end = torch.cuda.Event(enable_timing=True)
+ # roi_start.record()
+ # bbs_full_res = self.get_gpu_rois(
+ # inf_data["full_frame"],
+ # self.frame_count_target,
+ # inf_data["mask"],
+ # )
+ # # metrics["roi_time"] = (time.perf_counter() - t_start) * 1000
+ # # self.gpu_event_buffer["roi"].append((roi_start, roi_end))
+ # roi_end.record()
+ # self.gpu_event_buffer["roi"].append((roi_start, roi_end))
+ # metrics["bbs"] = bbs_full_res
+
+ # if self.config.DEBUG_FLAG:
+ # self.bgs_stream.waitForCompletion()
+ # display_source = inf_data["mask"]
+ # self.debug_save_mask(
+ # display_source, self.frame_count_target, rois=bbs_full_res
+ # )
+
+ # if bbs_full_res is not None and len(bbs_full_res) == 0:
+ # return num_objs, metrics
+
+ # det_start, det_end = (
+ # torch.cuda.Event(enable_timing=True),
+ # torch.cuda.Event(enable_timing=True),
+ # )
+ # det_start.record()
+ # if self.config.DETECTION_TYPE == "motion":
+ # # Motion Mode: Prepare boxes for drawing
+ # if (
+ # bbs_full_res is not None
+ # and bbs_full_res.ndim == 2
+ # and bbs_full_res.size(0) > 0
+ # ):
+ # scaled_resized_bbs = bbs_full_res / self.scales_tensor
+ # bbs_to_send = scaled_resized_bbs.detach().cpu().tolist()
+ # else:
+ # bbs_to_send = []
+ # data_to_draw = bbs_to_send
+ # num_objs = len(bbs_to_send)
+ # else:
+ # # Object Mode: Run YOLO and prepare metadata
+ # det_frame = (
+ # inf_data["full_frame"] if inf_data else device_frame
+ # ) # RGB
+ # metadata, _ = self.get_detections(
+ # det_frame,
+ # self.frame_count_target,
+ # merged=bbs_full_res,
+ # thickness=self.config.THICKNESS,
+ # device_input=self.config.device_input,
+ # )
+ # data_to_draw = metadata
+ # num_objs = len(metadata.keys())
+
+ # det_end.record()
+ # self.gpu_event_buffer["det"].append((det_start, det_end))
+
+ # # --- OFFLOAD TO QUEUE (Run for EVERY target frame) ---
+ # if not self.config.TEST_MODE:
+ # display_source = (
+ # inf_data["full_frame"]
+ # if (inf_data and "full_frame" in inf_data)
+ # else device_frame
+ # ) # RGB
+ # gpu_resized = F.interpolate(
+ # display_source.unsqueeze(0).half(),
+ # size=(self.resize_h, self.resize_w),
+ # mode="bilinear",
+ # align_corners=False,
+ # ).squeeze(0)
+ # display_frame = tensor2opencv(
+ # gpu_resized, self.config.device_input, is_bgr=True
+ # )
+ # # display_frame = self.tensor2opencv_gpu(gpu_resized)
+ # self.render_queue.put((display_frame, data_to_draw, self.label_source))
+
+ # if self.config.DEBUG_FLAG:
+ # gpu_resized = F.interpolate(
+ # display_source.unsqueeze(0).float(),
+ # size=(self.resize_h, self.resize_w),
+ # mode="bilinear",
+ # align_corners=False,
+ # ) # RGB
+ # self.debug_save_img(gpu_resized, self.frame_count_target)
+ # self.debug_save_img_roi(
+ # gpu_resized, bbs_full_res, self.frame_count_target
+ # )
+
+ # # Async metrics stay at 0 for fairness; collected at the end of the video
+ # metrics["sf_time"] = 0
+ # metrics["roi_time"] = 0
+ # metrics["det_time"] = 0
+ # return num_objs, metrics
+
+ # def yolo_cpu_pipeline_fn(self, device_frame, overall_frame_num, is_target_frame):
+ # num_objs = 0
+ # metadata = {}
+ # metrics = {"sf_time": 0, "roi_time": 0, "det_time": 0, "bbs": None}
+
+ # # Only keep frames for TARGET_FPS
+ # if is_target_frame:
+ # self.next_process_idx += self.step_size
+ # self.frame_count_target += 1 # 1-indexed
+
+ # # Get detection at original resolution (No SF)
+ # t_start = time.perf_counter()
+ # metadata, _ = self.get_detections(
+ # device_frame,
+ # self.frame_count_target,
+ # thickness=self.config.THICKNESS,
+ # device_input=self.config.device_input,
+ # )
+ # num_objs = len(metadata.keys())
+ # metrics["det_time"] = (time.perf_counter() - t_start) * 1000
+
+ # if not self.config.TEST_MODE:
+ # cpu_resized = cv2.resize(device_frame, (self.resize_w, self.resize_h))
+ # display_frame = tensor2opencv(
+ # cpu_resized, self.config.device_input, is_bgr=True
+ # )
+ # self.render_queue.put((display_frame, metadata, self.label_source))
+
+ # return num_objs, metrics
+
+ # def yolo_gpu_pipeline_fn(self, frame_raw, overall_frame_num, is_target_frame):
+ # num_objs = 0
+ # metadata = {}
+ # metrics = {"sf_time": 0, "roi_time": 0, "det_time": 0, "bbs": None}
+
+ # # Only keep frames for TARGET_FPS
+ # if is_target_frame:
+ # self.next_process_idx += self.step_size
+ # self.frame_count_target += 1 # 1-indexed
+
+ # # Get detection at original resolution (No SF)
+ # det_start, det_end = (
+ # torch.cuda.Event(enable_timing=True),
+ # torch.cuda.Event(enable_timing=True),
+ # )
+ # det_start.record()
+ # metadata, _ = self.get_detections(
+ # frame_raw,
+ # self.frame_count_target,
+ # thickness=self.config.THICKNESS,
+ # device_input=self.config.device_input,
+ # )
+ # num_objs = len(metadata.keys())
+ # det_end.record()
+ # self.gpu_event_buffer["det"].append((det_start, det_end))
+
+ # # --- OFFLOAD TO QUEUE (Run for EVERY target frame) ---
+ # if not self.config.TEST_MODE:
+ # gpu_resized = F.interpolate(
+ # frame_raw.unsqueeze(0).half(),
+ # size=(self.resize_h, self.resize_w),
+ # mode="bilinear",
+ # align_corners=False,
+ # ).squeeze(0)
+ # display_frame = tensor2opencv(
+ # gpu_resized, self.config.device_input, is_bgr=True
+ # )
+ # # display_frame = self.tensor2opencv_gpu(gpu_resized)
+ # self.render_queue.put((display_frame, metadata, self.label_source))
+
+ # # Async metrics stay at 0 for fairness; collected at the end of the video
+ # metrics["sf_time"] = 0
+ # metrics["det_time"] = 0
+ # return num_objs, metrics
+
+
+# INHERIT METHODS FROM HANDLERS -----------------------------------------------------------------
+# TestSmartFilteringDetections.setup_reader = DeviceBaseHandler.setup_reader
+# TestSmartFilteringDetections.get_frameWH = DeviceBaseHandler.get_frameWH
+# TestSmartFilteringDetections.initialize_variables = (
+# DeviceBaseHandler.initialize_variables
+# )
+# TestSmartFilteringDetections.filter_contained_boxes = (
+# DeviceBaseHandler.filter_contained_boxes
+# )
+# TestSmartFilteringDetections.get_detections = DeviceBaseHandler.get_detections
+# TestSmartFilteringDetections.prepare_pipeline = DeviceBaseHandler.prepare_pipeline
+# TestSmartFilteringDetections.get_gpu_rois_by_area = (
+# DeviceBaseHandler.get_gpu_rois_by_area
+# )
+# TestSmartFilteringDetections.get_gpu_rois = DeviceBaseHandler.get_gpu_rois
+# TestSmartFilteringDetections.get_cpu_rois = DeviceBaseHandler.get_cpu_rois
+# TestSmartFilteringDetections.run_model = DeviceBaseHandler.run_model
+# TestSmartFilteringDetections.model_warmup = DeviceBaseHandler.model_warmup
+
+# # TestSmartFilteringDetections.cleanup_gpu = GPUStreamHandler.cleanup_gpu
+# # TestSmartFilteringDetections.rbtd_full_gpu = GPUStreamHandler.rbtd_full_gpu
+# # TestSmartFilteringDetections.prepare_gpu_pipeline = (
+# # GPUStreamHandler.prepare_gpu_pipeline
+# # )
+# # TestSmartFilteringDetections.allocate_gpu = GPUStreamHandler.allocate_gpu
+# TestSmartFilteringDetections.gpu_warmup = GPUStreamHandler.gpu_warmup
+# TestSmartFilteringDetections.apply_background_subtraction_gpu = (
+# GPUStreamHandler.apply_background_subtraction_gpu
+# )
+
+# TestSmartFilteringDetections.cleanup_cpu = CPUStreamHandler.cleanup_cpu
+# # TestSmartFilteringDetections.rbtd_full_cpu = CPUStreamHandler.rbtd_full_cpu
+# # TestSmartFilteringDetections.prepare_cpu_pipeline = (
+# # CPUStreamHandler.prepare_cpu_pipeline
+# # )
+# # TestSmartFilteringDetections.allocate_cpu = CPUStreamHandler.allocate_cpu
+# TestSmartFilteringDetections.apply_background_subtraction_cpu = (
+# CPUStreamHandler.apply_background_subtraction_cpu
+# )
+
+
+def get_pytest_filter_expression(args):
+ print("\n" + "=" * 50)
+ print("TARGET SELECTION PREVIEW")
+ print("=" * 50)
+
+ filter_expression = "test_detections"
+ # applied_subs = []
+
+ if args.sf_enabled is not None:
+ # Target exact parameter tokens generated by pytest parametrization
+ sf_str = "-True-" if args.sf_enabled else "-False-"
+ filter_expression += f" and {sf_str}"
+ # applied_subs.append(f"sf_enabled={args.sf_enabled}")
+
+ if args.detection_type:
+ filter_expression += f" and {args.detection_type}"
+
+ # Target hardware context selection filter applies globally across all test cases
+ if args.device.lower() != "all":
+ print(f" 💻 Hardware Context Constraint: {args.device.upper()}")
+ filter_expression = f"({filter_expression}) and {args.device.lower()}"
+ else:
+ print(" 💻 Hardware Context Constraint: ALL AVAILABLE")
+
+ print("=" * 50)
+ print(f"COMPILED PYTEST KEYWORD EXPRESSION:\n 👉 {filter_expression}")
+ print("=" * 50 + "\n")
+
+ return filter_expression
+
+
+if __name__ == "__main__":
+ # TEST ARGUMENTS
+ parser = argparse.ArgumentParser(description="Run Video Detection Pipeline Tests")
+ parser.add_argument(
+ "-s",
+ "--source",
+ type=str,
+ default="anduril_swarm_8K.mp4",
+ help="Video filename (located in /inputs)",
+ )
+
+ # MODEL TO USE
+ parser.add_argument(
+ "--no-custom",
+ action="store_false",
+ dest="custom_model_flag",
+ help="Enable if using Ultralytics YOLO model",
+ )
+ parser.add_argument(
+ "-m",
+ "--model",
+ type=str,
+ default="drone_detection",
+ dest="model_name",
+ help="Name of model. Required if `--no-custom` is enabled. [Default: drone_detection]",
+ )
+
+ # Filter tests
+ parser.add_argument(
+ "--type",
+ type=str,
+ choices=["object", "motion"],
+ dest="detection_type",
+ help="Filter by detection type (object or motion)",
+ )
+ parser.add_argument(
+ "--device",
+ type=str,
+ default="all",
+ choices=["cpu", "gpu", "all"],
+ help="Filter by device (cpu or gpu)",
+ )
+ parser.add_argument(
+ "--sf",
+ action="store_true",
+ default=None,
+ dest="sf_enabled",
+ help="Filter by Smart Filtering",
+ )
+
+ # DEBUGGING
+ parser.add_argument(
+ "--debug",
+ action="store_true",
+ help="Enable debug message and save intermediate images for Smart Filtering tests",
+ )
+ parser.add_argument(
+ "-n",
+ type=int,
+ default=100,
+ dest="debug_frame_limit",
+ help="Number of frames used for debugging [Default: 100]",
+ )
+
+ args = parser.parse_args()
+
+ # UPDATE ENVIRONMENTAL VARIABLES
+ os.environ["VIDEO_FILENAME"] = args.source
+ os.environ["CUSTOM_MODEL_FLAG"] = "True" if args.custom_model_flag else "False"
+ os.environ["MODEL_NAME"] = args.model_name
+ os.environ["DEBUG"] = "1" if args.debug else "0"
+ os.environ["DEBUG_FRAME_LIMIT"] = str(args.debug_frame_limit)
+
+ # detection_type, device, sf_enabled
+ filter_expression = get_pytest_filter_expression(args)
+
+ # PYTEST COMMAND
+ pytest_args = [
+ "-k",
+ filter_expression,
+ "-s",
+ "-v",
+ # "--log-cli-level=DEBUG",
+ __file__,
+ ]
+
+ print(f"Launching tests for {args.source}")
+
+ sys.exit(pytest.main(pytest_args))
diff --git a/fastapi/tests/test_model.py b/fastapi/tests/test_model.py
new file mode 100644
index 0000000..c2710fd
--- /dev/null
+++ b/fastapi/tests/test_model.py
@@ -0,0 +1,441 @@
+import argparse
+import csv
+import gc
+import logging
+import os
+import sys
+import time
+from pathlib import Path
+
+import pytest
+import tensorrt as trt
+import torch
+
+sys.path.insert(1, str(Path(__file__).parent.parent))
+from include.default_configs import (
+ CUSTOM_MODEL_FLAG_DEFAULT,
+ DEBUG_DEFAULT,
+ MODEL_NAME_DEFAULT,
+ THRESHOLD_VALUE,
+)
+from include.handlers import DeviceBaseHandler
+from include.models import get_model
+from include.utils import PipelineConfig
+
+try:
+ torch.multiprocessing.set_start_method("spawn", force=True)
+except RuntimeError:
+ pass
+
+logging.getLogger("matplotlib").setLevel(logging.WARNING)
+logger = trt.Logger(trt.Logger.WARNING)
+trt.init_libnvinfer_plugins(logger, "")
+
+
+DEBUG_FLAG_DEFAULT = True if DEBUG_DEFAULT == "1" else False
+os.environ["OMP_NUM_THREADS"] = "1"
+
+force_export = False
+
+
+@pytest.fixture(scope="class")
+def setup_context(request):
+ """Replaces setUpClass: Runs once per test class."""
+ # Initialize shared paths/results
+ current_test_filename = Path(__file__).stem
+ test_dir = Path(__file__).parent
+ main_path = test_dir.parent
+ video_dir = main_path / "inputs"
+
+ VIDEO_FILENAME = os.getenv("VIDEO_FILENAME", "anduril_swarm_8K.mp4")
+ if video_dir.exists():
+ request.cls.video_path = video_dir / VIDEO_FILENAME
+ else:
+ video_dir = Path("/watch_dir")
+ request.cls.video_path = video_dir / VIDEO_FILENAME
+
+ # Add any shared state to the class
+ model_name = os.getenv("MODEL_NAME", MODEL_NAME_DEFAULT)
+ request.cls.result_dir = (
+ test_dir / f"{current_test_filename}_results/{request.cls.video_path.stem}"
+ )
+ request.cls.result_dir.mkdir(parents=True, exist_ok=True)
+
+ # Benchmark statistics
+ request.cls.benchmarks = []
+ request.cls.csv_filename = (
+ f"model_benchmarks_{model_name}_{request.cls.video_path.stem}.csv"
+ )
+ request.cls.csv_path = request.cls.result_dir / request.cls.csv_filename
+
+ # Initialize class vars
+ request.cls.name = request.cls.video_path.stem
+ request.cls.source = str(request.cls.video_path)
+ request.cls.is_rtsp = str(request.cls.source).startswith("rtsp:/")
+ request.cls.active = True
+ request.cls.active_streams = {}
+ request.cls._shared_model = None
+ request.cls._shared_model_path = None
+ request.cls._shared_model_device = None
+ request.cls._shared_model_sf_enabled = None
+
+ # RUN ALL PARAMETERIZED TESTS ----------------------------------------
+ yield
+
+ # FINAL CSV EXPORT --------------------------------------------------
+ if request.cls.benchmarks:
+ results = request.cls.benchmarks
+ keys = results[0].keys()
+
+ with open(str(request.cls.csv_path), "w", newline="") as f:
+ dict_writer = csv.DictWriter(f, fieldnames=keys)
+ dict_writer.writeheader()
+ dict_writer.writerows(results)
+
+ print(f"\n[FINAL] Benchmarks saved to {request.cls.csv_filename}", flush=True)
+
+
+@pytest.fixture(autouse=True)
+def each_test_setup(request):
+ test_class_self = request.instance
+
+ # setUp LOGIC --------------------------------------------------
+ if torch.cuda.is_available():
+ torch.cuda.synchronize()
+
+ device = request.node.callspec.params.get("device")
+ os.environ["DEVICE"] = device
+ detection_type = "object" # request.node.callspec.params.get("detection_type")
+ sf_enabled = False # request.node.callspec.params.get("sf_enabled")
+ model_name = os.getenv("MODEL_NAME", MODEL_NAME_DEFAULT)
+ test_class_self._testMethodName = f"{model_name}_{detection_type}_{device}"
+
+ render_dir = test_class_self.result_dir / f"{test_class_self._testMethodName}"
+ render_dir.mkdir(exist_ok=True)
+ test_class_self.config = PipelineConfig(
+ # GENERAL
+ SHARED_OUTPUT=render_dir, # os.getenv("SHARED_OUTPUT",SHARED_OUTPUT_DEFAULT),
+ CUSTOM_MODEL_FLAG=os.getenv(
+ "CUSTOM_MODEL_FLAG", CUSTOM_MODEL_FLAG_DEFAULT
+ ), # True,
+ DEVICE=device.upper(),
+ OMIT_DETECTIONS_FLAG=True,
+ TEST_MODE=False,
+ DEBUG=os.getenv("DEBUG", DEBUG_DEFAULT),
+ DEBUG_FRAME_LIMIT=os.getenv("DEBUG_FRAME_LIMIT", 100),
+ # VIDEO WRITER
+ # CLIP_DURATION=None,
+ # VDMS
+ ENABLE_QUERYING=False,
+ DBHOST="0.0.0.0",
+ DBPORT=55555,
+ # MODEL
+ MODEL_NAME=model_name,
+ # MODEL_H=360,
+ # PIPELINE
+ SMART_FILTERING_ENABLED=sf_enabled,
+ THRESHOLD_VALUE=int(os.getenv("THRESHOLD_VALUE", THRESHOLD_VALUE)),
+ # VISUALIZATION
+ DETECTION_TYPE=detection_type,
+ )
+
+ test_class_self.device = test_class_self.config.DEVICE
+ test_class_self.device_input = test_class_self.config.device_input
+ test_class_self.resize_h, test_class_self.resize_w = [
+ test_class_self.config.MODEL_H,
+ test_class_self.config.MODEL_W,
+ ]
+
+ test_class_self.setup_reader(
+ test_class_self.config.TARGET_FPS, test_class_self.config.CLIP_DURATION
+ )
+
+ # RUN PARAMETERIZED TEST ----------------------------------------
+ yield
+
+ # tearDown LOGIC --------------------------------------------------
+ print(
+ f"\n--- [TearDown] Memory Before Cleanup ({test_class_self._testMethodName}) ---"
+ )
+
+ # Nullify the model reference to trigger automatic cleanup
+ if hasattr(test_class_self, "model") and test_class_self.model is not None:
+ # Check if it's an Ultralytics model with a predictor
+ predictor = getattr(test_class_self.model, "predictor", None)
+ if predictor is not None:
+ try:
+ predictor.results = []
+ except AttributeError:
+ pass
+ del test_class_self.model
+ test_class_self.model = None
+
+ # Clear the singleton references to force a reload
+ TestSmartFilteringDetections._shared_model = None
+ TestSmartFilteringDetections._shared_model_path = None
+ TestSmartFilteringDetections._shared_model_device = None
+
+ # Force Python to run destructors NOW while streams are still alive
+ gc.collect()
+ if torch.cuda.is_available():
+ torch.cuda.synchronize() # Final sync to clear event queue
+ torch.cuda.empty_cache()
+ torch.cuda.ipc_collect() # Critical for shared memory cleanup
+ time.sleep(0.2)
+
+
+@pytest.mark.usefixtures("setup_context")
+class TestSmartFilteringDetections:
+ # SETUP --------------------------------------------
+
+ @pytest.mark.parametrize(
+ "device",
+ [
+ ("cpu"),
+ ("gpu"),
+ ],
+ )
+ def test_pipeline(self, device):
+ """Unified test runner for all configurations."""
+
+ # Run the actual model loader
+ self.get_model_by_device(device, sf_enabled=self.config.sf_enabled)
+
+ total_session_start = time.perf_counter()
+
+ results = self.model.predict(
+ source=self.source,
+ conf=self.config.DETECTION_THRESHOLD,
+ # iou=self.config.IOU_THRESHOLD,
+ show=False,
+ imgsz=(self.frame_height, self.frame_width),
+ save=True,
+ project=str(self.config.SHARED_OUTPUT),
+ name=f"pred_{self._testMethodName}_output_video",
+ exist_ok=True, # overwrite if folder exists
+ stream=True,
+ data={"names": {i: name for i, name in enumerate(self.label_source)}},
+ )
+
+ frame_cnt = 0
+ total_model_preprocess = 0.0
+ total_model_inference = 0.0
+ total_model_postprocess = 0.0
+ for result in results:
+ frame_cnt += 1
+ # Each iteration computes and yields the next frame's latency
+ total_model_preprocess += result.speed.get("preprocess", 0.0)
+ total_model_inference += result.speed.get("inference", 0.0)
+ total_model_postprocess += result.speed.get("postprocess", 0.0)
+
+ # Capture the true real-world duration across all operational processing layers
+ real_world_latency_ms = (time.perf_counter() - total_session_start) * 1000
+
+ self._finalize_benchmarks(
+ real_world_latency_ms,
+ frame_cnt,
+ total_model_preprocess,
+ total_model_inference,
+ total_model_postprocess,
+ )
+
+ # HELPERS --------------------------------------------
+ def get_model_by_device(self, device, sf_enabled=False):
+ """Singleton loader: only loads if device changes or model is missing."""
+ if (
+ sf_enabled
+ and (self.frame_width * self.frame_height)
+ <= self.config.SMART_FILTERING_PIXEL_CONSTRAINT
+ ):
+ sf_enabled = False
+
+ if (
+ TestSmartFilteringDetections._shared_model is not None
+ and TestSmartFilteringDetections._shared_model_device == device
+ and TestSmartFilteringDetections._shared_model_sf_enabled == sf_enabled
+ ):
+ self.model = TestSmartFilteringDetections._shared_model
+ self.model_path = TestSmartFilteringDetections._shared_model_path
+ return
+
+ run_platform_name = "engine" if "cuda" in self.device_input else "openvino"
+
+ if self.config.CUSTOM_MODEL_FLAG:
+ dir_path = "/home/resources/models/ultralytics/custom_models"
+ else:
+ dir_path = f"/home/resources/models/ultralytics/{self.config.MODEL_NAME}/{self.config.MODEL_PRECISION}"
+
+ (
+ TestSmartFilteringDetections._shared_model,
+ TestSmartFilteringDetections._shared_model_path,
+ self.label_source,
+ ) = get_model(
+ Path(dir_path),
+ self.config.MODEL_NAME,
+ run_platform_name,
+ self.device_input,
+ batch=self.config.MODEL_MAX_BATCH_SIZE,
+ force_export=force_export,
+ sf_enabled=sf_enabled,
+ model_h=self.resize_h,
+ model_w=self.resize_w,
+ )
+
+ TestSmartFilteringDetections._shared_model_device = device
+ TestSmartFilteringDetections._shared_model_sf_enabled = sf_enabled
+ self.model = TestSmartFilteringDetections._shared_model
+ # self.model.half()
+ self.model_path = TestSmartFilteringDetections._shared_model_path
+
+ W, H = self.resize_w, self.resize_h
+ if not sf_enabled:
+ W, H = self.frame_width, self.frame_height
+ self.model_warmup(H, W)
+
+ def _finalize_benchmarks(
+ self,
+ real_world_latency_ms,
+ n_frames,
+ total_model_preprocess,
+ total_model_inference,
+ total_model_postprocess,
+ ):
+ """Aggregates metrics and adds them to the results list."""
+ total_model_ms = (
+ total_model_preprocess + total_model_inference + total_model_postprocess
+ )
+
+ duration_s = n_frames / self.input_fps if self.input_fps > 0 else 0
+
+ real_latency_s = real_world_latency_ms / 1000.0
+ real_est_fps = n_frames / real_latency_s if real_latency_s > 0 else 0
+ model_fps = n_frames / (total_model_ms / 1000.0) if total_model_ms > 0 else 0
+
+ # Construct dictionary block matching test_detections structure
+ stats = {
+ "Test Name": self._testMethodName,
+ "Detection Type": self.config.DETECTION_TYPE,
+ "Device": self.device,
+ "Smart Filtering": "Enabled" if self.config.sf_enabled else "Disabled",
+ "Video": self.video_path.name,
+ "Video Duration (s)": f"{duration_s:.4f}",
+ "Video FPS": f"{self.input_fps:.2f}",
+ "Pipeline Latency (s)": f"{real_latency_s:.2f}",
+ "Frames Processed": n_frames,
+ "Real Est. FPS": f"{real_est_fps:.2f}",
+ "Model Est. FPS": f"{model_fps:.2f}",
+ "Model Avg Pre-processing (ms)": f"{total_model_preprocess / n_frames:.2f}",
+ "Model Avg Inference (ms)": f"{total_model_inference / n_frames:.2f}",
+ "Model Avg Post-processing (ms)": f"{total_model_postprocess / n_frames:.2f}",
+ }
+ self.__class__.benchmarks.append(stats)
+ print(stats, flush=True)
+
+ print(f"\n[{self._testMethodName}] Latency: {real_latency_s:.2f} sec")
+ print(f"\n[{self._testMethodName}] Real Est. FPS: {real_est_fps:.2f}")
+ print(
+ f"\n[{self._testMethodName}] Model Est. FPS: {model_fps:.2f} ({n_frames} frames)"
+ )
+
+
+# INHERIT METHODS FROM HANDLERS -----------------------------------------------------------------
+TestSmartFilteringDetections.setup_reader = DeviceBaseHandler.setup_reader
+TestSmartFilteringDetections.get_frameWH = DeviceBaseHandler.get_frameWH
+TestSmartFilteringDetections.run_model = DeviceBaseHandler.run_model
+TestSmartFilteringDetections.model_warmup = DeviceBaseHandler.model_warmup
+
+
+if __name__ == "__main__":
+ # TEST ARGUMENTS
+ parser = argparse.ArgumentParser(description="Run Video Detection Pipeline Tests")
+ parser.add_argument(
+ "-s",
+ "--source",
+ type=str,
+ default="anduril_swarm_8K.mp4",
+ help="Video filename (located in /inputs) or RTSP target stream endpoint",
+ )
+
+ # MODEL TO USE
+ parser.add_argument(
+ "--no-custom",
+ action="store_false",
+ dest="custom_model_flag",
+ help="Enable if using Ultralytics YOLO model",
+ )
+ parser.add_argument(
+ "-m",
+ "--model",
+ type=str,
+ default="drone_detection",
+ dest="model_name",
+ help="Name of model. Required if `--no-custom` is enabled. [Default: drone_detection]",
+ )
+
+ # Filter tests
+ # parser.add_argument(
+ # "--type",
+ # type=str,
+ # choices=["object", "motion"],
+ # dest="detection_type",
+ # help="Filter by detection type (object or motion)",
+ # )
+ parser.add_argument(
+ "--device",
+ type=str,
+ choices=["cpu", "gpu"],
+ help="Filter by device (cpu or gpu)",
+ )
+ # parser.add_argument(
+ # "--sf",
+ # action="store_true",
+ # default=None,
+ # dest="sf_enabled",
+ # help="Filter by Smart Filtering",
+ # )
+
+ # # DEBUGGING
+ # parser.add_argument(
+ # "--debug",
+ # action="store_true",
+ # help="Enable debug message and save intermediate images for Smart Filtering tests",
+ # )
+ # parser.add_argument(
+ # "-n",
+ # type=int,
+ # default=100,
+ # dest="debug_frame_limit",
+ # help="Number of frames used for debugging [Default: 100]",
+ # )
+
+ args = parser.parse_args()
+
+ # UPDATE ENVIRONMENTAL VARIABLES
+ os.environ["VIDEO_FILENAME"] = args.source
+ os.environ["CUSTOM_MODEL_FLAG"] = "True" if args.custom_model_flag else "False"
+ os.environ["MODEL_NAME"] = args.model_name
+ # os.environ["DEBUG"] = "1" if args.debug else "0"
+ # os.environ["DEBUG_FRAME_LIMIT"] = str(args.debug_frame_limit)
+
+ # detection_type, device, sf_enabled
+ run_args = []
+ # if args.detection_type:
+ # run_args.append(args.detection_type)
+ if args.device:
+ run_args.append(args.device)
+ # if args.sf_enabled:
+ # run_args.append(str(args.sf_enabled))
+
+ # PYTEST COMMAND
+ pytest_args = [
+ "-s",
+ "-v",
+ "--log-cli-level=DEBUG",
+ __file__,
+ ] # -s -v --log-cli-level=DEBUG
+ if run_args:
+ pytest_args.extend(["-k", " and ".join(run_args)])
+
+ print(f"Launching tests for {args.source}")
+
+ sys.exit(pytest.main(pytest_args))
diff --git a/fastapi/tests/test_pipeline.py b/fastapi/tests/test_pipeline.py
new file mode 100644
index 0000000..fb924dd
--- /dev/null
+++ b/fastapi/tests/test_pipeline.py
@@ -0,0 +1,868 @@
+import argparse
+import csv
+import gc
+import logging
+import multiprocessing
+import os
+import sys
+import time
+import traceback
+from datetime import datetime
+from pathlib import Path
+
+import psutil
+import pytest
+import torch
+
+sys.path.insert(1, str(Path(__file__).parent.parent))
+from include.default_configs import (
+ CUSTOM_MODEL_FLAG_DEFAULT,
+ DEBUG_DEFAULT,
+ MODEL_NAME_DEFAULT,
+ THRESHOLD_VALUE,
+)
+
+# Import the exact core handlers to replace duplicate script code
+# from include.handlers import BASE_PIPELINE_CONFIG
+from include.utils import (
+ PipelineConfig,
+)
+
+try:
+ torch.multiprocessing.set_start_method("spawn", force=True)
+except RuntimeError:
+ pass
+
+
+logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ handlers=[logging.StreamHandler(sys.stdout)],
+)
+# Suppress low-delay reference block warnings from OpenCV
+os.environ["OPENCV_LOG_LEVEL"] = "OFF"
+
+main_app_logger = logging.getLogger(__name__)
+
+
+def log_to_logger(message, level="info"):
+ try:
+ if level.lower() == "debug":
+ main_app_logger.debug(message)
+ elif level.lower() == "warning":
+ main_app_logger.warning(message)
+ else:
+ main_app_logger.info(message)
+ except Exception:
+ pass
+
+
+@pytest.fixture(scope="class")
+def setup_context(request):
+ """Orchestrates test directories and configuration schemas."""
+ current_test_filename = Path(__file__).stem
+ test_dir = Path(__file__).parent
+ main_path = test_dir.parent
+ video_dir = main_path / "inputs"
+
+ # Resolve source from CLI/Environment parameters
+ request.cls.source = os.getenv("STREAM_SOURCE", "anduril_swarm_8K.mp4")
+ is_rtsp = "rtsp://" in request.cls.source
+ if not is_rtsp:
+ VIDEO_FILENAME = request.cls.source
+ if video_dir.exists():
+ vid_source = video_dir / VIDEO_FILENAME
+ else:
+ video_dir = Path("/watch_dir")
+ vid_source = video_dir / VIDEO_FILENAME
+
+ assert vid_source.exists()
+ request.cls.source = str(vid_source)
+ request.cls.name = vid_source.stem
+ else:
+ request.cls.name = "rtsp"
+ request.cls.test_duration_mins = float(os.getenv("TEST_DURATION_MINS", 2.0))
+
+ request.cls.result_dir = test_dir / f"{current_test_filename}_results"
+ request.cls.result_dir.mkdir(parents=True, exist_ok=True)
+
+ request.cls.benchmarks = []
+ if is_rtsp:
+ request.cls.csv_filename = "reader_perf_results_rtsp.csv"
+ else:
+ vid_shortname = Path(request.cls.source).stem
+ request.cls.csv_filename = f"reader_perf_results_{vid_shortname}.csv"
+ request.cls.csv_path = request.cls.result_dir / request.cls.csv_filename
+
+ yield
+
+ if request.cls.benchmarks:
+ ordered_headers = [
+ "timestamp",
+ "test_name",
+ "source",
+ "device",
+ "detection_type",
+ "smart_filter_active",
+ "configured_duration_mins",
+ "video_duration",
+ "actual_duration_secs",
+ "stat_duration_secs",
+ "hardware_video_fps",
+ "pipeline_read_fps",
+ "stat_fps",
+ "total_frames_read",
+ "stat_frame_count",
+ "total_frames_ingested",
+ "total_target_frames_processed",
+ "total_objects_detected",
+ "frames_dropped_or_skipped",
+ "dropped_frame_sequences",
+ "average_read_latency_ms",
+ "max_read_latency_ms",
+ "avg_cpu_utilization_pct",
+ "avg_system_ram_used_mb",
+ "avg_gpu_vram_allocated_mb",
+ "prefetch_queue_backlog",
+ "avg_prefetch_backlog_frames",
+ "hardware_fallback_triggers",
+ "fallback_engine_triggered",
+ "status",
+ ]
+ # keys = {k for r in request.cls.benchmarks for k in r.keys()}
+ keys = []
+ for r in request.cls.benchmarks:
+ for k in r.keys():
+ keys.append(k)
+ sorted_keys = []
+ for c in ordered_headers:
+ if c in keys:
+ sorted_keys.append(c)
+ with open(str(request.cls.csv_path), "w", newline="") as f:
+ writer = csv.DictWriter(f, fieldnames=list(sorted_keys))
+ writer.writeheader()
+ writer.writerows(request.cls.benchmarks)
+ print(f"\n[FINAL] Telemetry saved to: {request.cls.csv_path}", flush=True)
+
+
+@pytest.fixture(autouse=True)
+def each_test_setup(request):
+ if torch.cuda.is_available():
+ torch.cuda.synchronize()
+
+ device = request.node.callspec.params.get("device")
+ os.environ["DEVICE"] = device
+
+ yield
+
+ gc.collect()
+ if torch.cuda.is_available():
+ torch.cuda.synchronize()
+ torch.cuda.empty_cache()
+ torch.cuda.ipc_collect()
+ time.sleep(0.2)
+
+
+def stream_worker(
+ test_name,
+ source,
+ source_name,
+ out_dir,
+ device_type,
+ test_duration_mins,
+ result_queue,
+ run_clipper,
+ disable_detection=True,
+ sf_enabled=True,
+ detection_type="object",
+):
+ """
+ Subprocess sandbox that bridges metrics capture straight to the production
+ DeviceBaseHandler pipeline engine.
+ """
+
+ # Suppress OpenCV internal warn frames
+ os.environ["OPENCV_LOG_LEVEL"] = "OFF"
+ os.environ["OPENCV_VIDEOIO_DEBUG"] = "0"
+ os.environ["FFMPEG_LOG_LEVEL"] = "quiet"
+ test_duration_secs = test_duration_mins * 60
+ metrics = {
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+ "test_name": test_name,
+ "source": source,
+ "device": device_type,
+ "video_duration": 0.0,
+ # "pipeline_read_fps": 0.0,
+ # "avg_cpu_utilization_pct": 0.0,
+ # "avg_system_ram_used_mb": 0.0,
+ # "avg_gpu_vram_allocated_mb": 0.0,
+ # "status": "INIT",
+ "configured_duration_mins": test_duration_mins,
+ "actual_duration_secs": 0.0,
+ "stat_duration_secs": 0.0,
+ "hardware_video_fps": 0.0,
+ "pipeline_read_fps": 0.0,
+ "stat_fps": 0.0,
+ "total_frames_read": 0,
+ "stat_frame_count": 0,
+ "frames_dropped_or_skipped": 0,
+ "dropped_frame_sequences": 0,
+ "average_read_latency_ms": 0.0,
+ "avg_cpu_utilization_pct": 0.0,
+ "avg_system_ram_used_mb": 0.0,
+ "avg_gpu_vram_allocated_mb": 0.0,
+ "prefetch_queue_backlog": 0, # how full the thread queue is
+ "hardware_fallback_triggers": 0, # times reader swaps from NVDEC to software CPU mode due to error flags
+ "max_read_latency_ms": 0.0,
+ "avg_prefetch_backlog_frames": 0.0,
+ "fallback_engine_triggered": 0,
+ "total_frames_ingested": 0,
+ "total_target_frames_processed": 0,
+ "total_objects_detected": 0,
+ "detection_type": detection_type,
+ "smart_filter_active": sf_enabled,
+ "status": "INIT",
+ }
+
+ loop_start = time.perf_counter()
+ process = psutil.Process(os.getpid())
+ cpu_samples, ram_samples, vram_samples = [], [], []
+ prefetch_backlog_samples = []
+
+ # Override the global configuration mapping for this specific hardware context
+ config = PipelineConfig(
+ # GENERAL
+ CUSTOM_MODEL_FLAG=os.getenv(
+ "CUSTOM_MODEL_FLAG", CUSTOM_MODEL_FLAG_DEFAULT
+ ), # True,
+ DEVICE="GPU" if device_type.lower() == "gpu" else "CPU",
+ OMIT_DETECTIONS_FLAG=True,
+ TEST_MODE=True,
+ DEBUG=os.getenv("DEBUG", DEBUG_DEFAULT),
+ DEBUG_FRAME_LIMIT=os.getenv("DEBUG_FRAME_LIMIT", 100),
+ # VIDEO WRITER
+ # CLIP_DURATION=None,
+ # VDMS
+ ENABLE_QUERYING=run_clipper,
+ DISABLE_DETECTION=disable_detection,
+ DBHOST="127.0.0.1",
+ # MODEL
+ MODEL_NAME=os.getenv("MODEL_NAME", MODEL_NAME_DEFAULT),
+ # MODEL_H=360,
+ # PIPELINE
+ SMART_FILTERING_ENABLED=sf_enabled,
+ THRESHOLD_VALUE=int(os.getenv("THRESHOLD_VALUE", THRESHOLD_VALUE)),
+ # VISUALIZATION
+ DETECTION_TYPE=detection_type,
+ # MAX_WORKERS=4,
+ )
+
+ if out_dir:
+ if "Scenario_4_" in test_name:
+ result_dir = out_dir / "results"
+ result_dir.mkdir(parents=True, exist_ok=True)
+ os.environ["TEST_SUITE_RENDER_DIR"] = str(result_dir)
+ config.SHARED_OUTPUT = str(out_dir)
+
+ import vdms
+
+ def mock_connect(self, host, port):
+ pass
+
+ vdms.vdms.connect = mock_connect
+
+ from include.handlers import CPUStreamHandler, GPUStreamHandler
+
+ if device_type.lower() == "gpu":
+ HandlerClass = GPUStreamHandler
+ else:
+ HandlerClass = CPUStreamHandler
+
+ last_sample = time.perf_counter()
+ handler = None # Explicit initializing tracking state pointer 🚀
+
+ try:
+ # Let handlers.py completely manage the reader, threads, ring buffers, and FFMPEG process
+ handler = HandlerClass(
+ source=source, name=source_name, active_streams={}, config=config
+ )
+ handler.start()
+
+ while (time.perf_counter() - loop_start) < test_duration_secs:
+ if not handler.active:
+ break
+
+ curr_time = time.perf_counter()
+ if (curr_time - last_sample) >= 0.5:
+ cpu_samples.append(psutil.cpu_percent(interval=0))
+ ram_samples.append(process.memory_info().rss / (1024 * 1024))
+ if device_type.lower() == "gpu" and torch.cuda.is_available():
+ vram_free, vram_total = torch.cuda.mem_get_info()
+ vram_samples.append((vram_total - vram_free) / (1024 * 1024))
+ if getattr(handler, "prefetch_queue", None) is not None:
+ current_backlog = handler.prefetch_queue.qsize()
+ prefetch_backlog_samples.append(current_backlog)
+ last_sample = curr_time
+ time.sleep(0.01)
+
+ # Safely capture performance values out of production thread states
+ actual_duration = time.perf_counter() - loop_start
+ metrics["actual_duration_secs"] = actual_duration
+ metrics["stat_frame_count"] = handler.stat_frame_count
+ metrics["stat_fps"] = handler.stat_fps
+ metrics["stat_duration_secs"] = (
+ round(handler.stat_frame_count / handler.stat_fps, 2)
+ if getattr(handler, "stat_fps", 0) > 0
+ else 0.0
+ )
+ metrics["status"] = "COMPLETED_SUCCESSFULLY"
+ metrics["total_frames_ingested"] = (
+ handler.frame_count
+ ) # Total raw frames processed
+ metrics["total_target_frames_processed"] = (
+ handler.frame_count_target
+ ) # Total target slices saved/evaluated
+ metrics["total_objects_detected"] = handler.total_objects_detected
+
+ metrics["total_frames_read"] = getattr(handler, "frame_count", 0)
+
+ metrics["smart_filter_active"] = sf_enabled
+ metrics["device"] = device_type
+ metrics["detection_type"] = detection_type
+
+ if "Scenario_4_" in test_name and handler.frame_count == 0:
+ metrics["status"] = "FAILED_NO_FRAMES"
+
+ # Pull counters natively tracked by the underlying HybridReaders
+ if hasattr(handler, "reader") and handler.reader is not None:
+ r = handler.reader
+ metrics["hardware_video_fps"] = round(getattr(r, "input_fps", 0.0), 2)
+ metrics["frames_dropped_or_skipped"] = getattr(r, "dropped_frames_count", 0)
+ metrics["dropped_frame_sequences"] = getattr(
+ r, "dropped_sequences_count", 0
+ )
+
+ metrics["hardware_fallback_triggers"] = (
+ 1 if getattr(r, "use_cpu_decode_fallback", False) else 0
+ )
+ metrics["fallback_engine_triggered"] = (
+ 1 if getattr(r, "use_cpu_decode_fallback", False) else 0
+ )
+ if not str(handler.source).startswith("rtsp://"):
+ metrics["video_duration"] = (
+ round(r.numFrames / metrics["hardware_video_fps"], 2)
+ if metrics["hardware_video_fps"] > 0
+ else 0.0
+ )
+
+ h_telemetry = getattr(handler, "telemetry", {})
+ io_latencies = h_telemetry.get("ram_disk_io_write_ms", [])
+
+ if io_latencies:
+ metrics["average_read_latency_ms"] = round(
+ sum(io_latencies) / len(io_latencies), 2
+ )
+ metrics["max_read_latency_ms"] = round(max(io_latencies), 2)
+ else:
+ # Fallback estimation based on frame process intervals if disk I/O lists were bypassed
+ estimated_latency = (
+ actual_duration / max(1, metrics["total_frames_read"])
+ ) * 1000
+ metrics["average_read_latency_ms"] = round(estimated_latency, 2)
+ metrics["max_read_latency_ms"] = round(estimated_latency * 1.4, 2)
+
+ # Calculate averages from telemetry sampling windows
+ metrics["pipeline_read_fps"] = (
+ round(metrics["total_frames_read"] / actual_duration, 2)
+ if actual_duration > 0
+ else 0.0
+ )
+
+ except Exception as err:
+ is_expected_fail = (
+ "Scenario_1" in test_name
+ and isinstance(err, (RuntimeError, TimeoutError))
+ and any(
+ x in str(err).lower()
+ for x in [
+ "could not open/connect",
+ "failed to initialize stream reader endpoint",
+ "stream reader initialization failure",
+ "timed out",
+ ]
+ )
+ )
+ if is_expected_fail:
+ log_to_logger(
+ f"[EXPECTED FAILURE SUCCESSFUL]: {test_name} handled invalid stream target.",
+ level="info",
+ )
+ metrics["status"] = "PASSED_RECONNECT_FAIL"
+ metrics["actual_duration_secs"] = round(time.perf_counter() - loop_start, 2)
+ else:
+ log_to_logger(
+ f"[WORKER CRASHED]:\n{traceback.format_exc()}",
+ level="warning",
+ )
+ metrics["status"] = f"CRASHED: {type(err).__name__}"
+ finally:
+ # Calculate system baseline averages across the sample tracking matrices safely
+ if cpu_samples:
+ metrics["avg_cpu_utilization_pct"] = round(
+ sum(cpu_samples) / len(cpu_samples), 1
+ )
+ if ram_samples:
+ metrics["avg_system_ram_used_mb"] = round(
+ sum(ram_samples) / len(ram_samples), 1
+ )
+ if vram_samples:
+ metrics["avg_gpu_vram_allocated_mb"] = round(
+ sum(vram_samples) / len(vram_samples), 1
+ )
+ if prefetch_backlog_samples:
+ metrics["prefetch_queue_backlog"] = prefetch_backlog_samples[-1]
+ metrics["avg_prefetch_backlog_frames"] = round(
+ sum(prefetch_backlog_samples) / len(prefetch_backlog_samples), 2
+ )
+
+ # Cleanup active thread worker contexts safely if they exist
+ if handler is not None and getattr(handler, "active", False):
+ try:
+ handler.stop()
+ except Exception:
+ pass
+
+ # Provide a 200ms cool-down window for OpenVINO/PyTorch C++ worker threads
+ # to finish their internal teardown before Python destroys the process space.
+ time.sleep(0.2)
+
+ gc.collect()
+ if torch.cuda.is_available():
+ try:
+ torch.cuda.synchronize()
+ torch.cuda.empty_cache()
+ torch.cuda.ipc_collect()
+ except Exception:
+ pass
+
+ result_queue.put(metrics)
+
+ # Absolute kill switch safely reclaims orphaned third-party threads
+ # at the kernel level without crashing the parent pytest framework
+ time.sleep(0.1)
+ os._exit(0)
+
+
+@pytest.mark.usefixtures("setup_context")
+class TestHybridStreamHandlers:
+ @pytest.mark.parametrize("device", ["cpu", "gpu"])
+ def test_scenario_1_invalid_rtsp(self, device):
+ """SCENARIO 1: Automated Connection Fail Simulation"""
+ test_name = "Scenario_1_Invalid_RTSP"
+ bad_uri = "rtsp://invalid_host_domain:554/stream_simulation"
+ run_clipper = False
+ time_limit_m = round(self.test_duration_mins, 1)
+
+ print(
+ f"\n========================================\n"
+ f"RUNNING TEST: {test_name} | Device: {device.upper()}\n"
+ f"Source Destination: {bad_uri}\n"
+ f"========================================",
+ flush=True,
+ )
+
+ # Execute production workflow in completely isolated spawned sandbox
+ ctx = multiprocessing.get_context("spawn")
+ res_queue = ctx.Queue()
+
+ worker_p = ctx.Process(
+ target=stream_worker,
+ args=(
+ test_name,
+ bad_uri,
+ self.name,
+ None,
+ device,
+ time_limit_m,
+ res_queue,
+ run_clipper,
+ ),
+ )
+ worker_p.start()
+ worker_p.join()
+
+ if worker_p.is_alive():
+ worker_p.terminate()
+ worker_p.close() # Reclaims underlying OS file descriptors immediately
+
+ test_metrics = res_queue.get()
+ self.__class__.benchmarks.append(test_metrics)
+ print(f"Test Status Result: {test_metrics.get('status')}\n", flush=True)
+
+ # Explicitly tear down queue thread pools to prevent resource tracking leaks
+ try:
+ res_queue.close() # Stops new items from being inserted
+ res_queue.join_thread() # Joins the internal buffer thread safely
+ except Exception:
+ pass
+
+ assert any(x in test_metrics.get("status") for x in ["PASSED", "ABORT"])
+
+ @pytest.mark.parametrize("device", ["cpu", "gpu"])
+ def test_scenario_2_longevity_throughput(self, device):
+ """SCENARIO 2: Stability & Throughput Run"""
+ test_name = "Scenario_2_Longevity_Throughput_Evaluation"
+ run_clipper = False
+
+ print(
+ f"\n========================================\n"
+ f"RUNNING TEST: {test_name} | Device: {device.upper()}\n"
+ f"Source Destination: {self.source}\n"
+ f"========================================",
+ flush=True,
+ )
+
+ # Execute production workflow in completely isolated spawned sandbox
+ ctx = multiprocessing.get_context("spawn")
+ res_queue = ctx.Queue()
+
+ worker_p = ctx.Process(
+ target=stream_worker,
+ args=(
+ test_name,
+ self.source,
+ self.name,
+ None,
+ device,
+ self.test_duration_mins,
+ res_queue,
+ run_clipper,
+ ),
+ )
+ worker_p.start()
+ worker_p.join()
+
+ if worker_p.is_alive():
+ worker_p.terminate()
+ worker_p.close() # Reclaims underlying OS file descriptors immediately
+
+ test_metrics = res_queue.get()
+ self.__class__.benchmarks.append(test_metrics)
+ print(f"Test Status Result: {test_metrics.get('status')}\n", flush=True)
+
+ # Explicitly tear down queue thread pools to prevent resource tracking leaks
+ try:
+ res_queue.close() # Stops new items from being inserted
+ res_queue.join_thread() # Joins the internal buffer thread safely
+ except Exception:
+ pass
+
+ assert "COMPLETED_SUCCESSFULLY" in test_metrics.get("status")
+
+ @pytest.mark.parametrize("device", ["cpu", "gpu"])
+ def test_scenario_3_video_clipper(self, device):
+ """SCENARIO 3: Minimized Clip Generation Test via Production Handlers."""
+ test_name = f"Scenario_3_Clipper_{device.upper()}"
+ # test_name = "Scenario_3_Clip_Generation_Evaluation"
+ run_clipper = True
+
+ print(
+ f"\n========================================\n"
+ f"RUNNING TEST: {test_name} | Device: {device.upper()}\n"
+ f"Source Destination: {self.source}\n"
+ f"========================================",
+ flush=True,
+ )
+ render_dir = self.result_dir / f"{self.name}/scenario3_{device}"
+ render_dir.mkdir(parents=True, exist_ok=True)
+ test_duration_mins = 1.0
+
+ # Execute production workflow in completely isolated spawned sandbox
+ ctx = multiprocessing.get_context("spawn")
+ res_queue = ctx.Queue()
+
+ worker_p = ctx.Process(
+ target=stream_worker,
+ args=(
+ test_name,
+ self.source,
+ self.name,
+ render_dir,
+ device,
+ test_duration_mins,
+ res_queue,
+ run_clipper,
+ ),
+ )
+ worker_p.start()
+ worker_p.join()
+
+ if worker_p.is_alive():
+ worker_p.terminate()
+ worker_p.close() # Reclaims underlying OS file descriptors immediately
+
+ test_metrics = res_queue.get()
+ self.__class__.benchmarks.append(test_metrics)
+ print(f"Test Status Result: {test_metrics.get('status')}\n", flush=True)
+
+ # Explicitly tear down queue thread pools to prevent resource tracking leaks
+ try:
+ res_queue.close() # Stops new items from being inserted
+ res_queue.join_thread() # Joins the internal buffer thread safely
+ except Exception:
+ pass
+
+ assert "COMPLETED_SUCCESSFULLY" in test_metrics.get("status")
+
+ @pytest.mark.parametrize("device", ["cpu", "gpu"])
+ @pytest.mark.parametrize("sf_enabled", [True, False])
+ @pytest.mark.parametrize("detection_type", ["motion", "object"])
+ def test_scenario_4_detection_and_clipper(self, device, sf_enabled, detection_type):
+ """SCENARIO 4: Pipeline without sending metadata (detection + video clipper)."""
+ mode_str = "SmartFilter" if sf_enabled else "OnlyYOLO"
+ test_name = f"Scenario_4_{device.upper()}_{mode_str}"
+ run_clipper = True
+ disable_detection = False
+
+ print(
+ f"\n========================================\n"
+ f"RUNNING TEST: {test_name} | Device: {device.upper()} | SF: {sf_enabled}\n"
+ f"Source Name: {self.name} | Destination: {self.source}\n"
+ f"========================================",
+ flush=True,
+ )
+ render_dir = (
+ self.result_dir
+ / f"{self.name}/scenario4_{device}/{detection_type}_{mode_str}"
+ )
+ render_dir.mkdir(parents=True, exist_ok=True)
+ test_duration_mins = 1.0
+
+ # Execute production workflow in completely isolated spawned sandbox
+ ctx = multiprocessing.get_context("spawn")
+ res_queue = ctx.Queue()
+
+ worker_p = ctx.Process(
+ target=stream_worker,
+ args=(
+ test_name,
+ self.source,
+ self.name,
+ render_dir,
+ device,
+ test_duration_mins,
+ res_queue,
+ run_clipper,
+ disable_detection,
+ sf_enabled,
+ detection_type,
+ ),
+ )
+ worker_p.start()
+ worker_p.join()
+
+ if worker_p.is_alive():
+ worker_p.terminate()
+ worker_p.close() # Reclaims underlying OS file descriptors immediately
+
+ test_metrics = res_queue.get()
+ self.__class__.benchmarks.append(test_metrics)
+ if disable_detection:
+ print(f"Test Status Result: {test_metrics.get('status')}\n", flush=True)
+ else:
+ print(
+ f"Test Status Result: {test_metrics.get('status')} w/ {test_metrics.get('total_objects_detected')} detections\n",
+ flush=True,
+ )
+
+ # Explicitly tear down queue thread pools to prevent resource tracking leaks
+ try:
+ res_queue.close() # Stops new items from being inserted
+ res_queue.join_thread() # Joins the internal buffer thread safely
+ except Exception:
+ pass
+
+ assert "COMPLETED_SUCCESSFULLY" in test_metrics.get("status")
+
+
+def get_available_scenarios():
+ import inspect
+
+ available_scenarios = set()
+ for attr_name, _ in inspect.getmembers(
+ TestHybridStreamHandlers, predicate=inspect.isfunction
+ ):
+ if attr_name.startswith("test_scenario_"):
+ # Extracts '1' from 'test_scenario_1_invalid_rtsp'
+ parts = attr_name.split("_")
+ if len(parts) > 2 and parts[2].isdigit():
+ available_scenarios.add(int(parts[2]))
+
+ return sorted(list(available_scenarios))
+
+
+def get_pytest_filter_expression(args, sorted_scenarios):
+ # Automatically separate scenarios that support filtering vs basic scenarios
+ parameterized_ids = {4}
+ basic_ids = [num for num in sorted_scenarios if num not in parameterized_ids]
+
+ # Determine which scenarios the user wants to filter over
+ target_scenarios = args.scenario if args.scenario else sorted_scenarios
+
+ scenario_clauses = []
+ print("\n" + "=" * 50)
+ print("TARGET SELECTION PREVIEW")
+ print("=" * 50)
+
+ for num in target_scenarios:
+ if num in basic_ids:
+ print(f" 🔹 Scenario {num}: Standard routing (ignoring sub-filters)")
+ scenario_clauses.append(f"scenario_{num}")
+ else:
+ clause = f"scenario_{num}"
+ applied_subs = []
+
+ if args.sf_enabled is not None:
+ # Target exact parameter tokens generated by pytest parametrization
+ sf_str = "-True-" if args.sf_enabled else "-False-"
+ clause += f" and {sf_str}"
+ applied_subs.append(f"sf_enabled={args.sf_enabled}")
+
+ if args.detection_type:
+ clause += f" and {args.detection_type}"
+ applied_subs.append(f"type={args.detection_type}")
+
+ sub_msg = (
+ f" with sub-filters: {', '.join(applied_subs)}" if applied_subs else ""
+ )
+ print(f" ⚙️ Scenario {num}: Active compilation{sub_msg}")
+ scenario_clauses.append(f"({clause})")
+
+ # Safely join scenarios together with 'or' so they can execute side-by-side
+ filter_expression = f"test_scenario_ and ({' or '.join(scenario_clauses)})"
+
+ # Target hardware context selection filter applies globally across all test cases
+ if args.device.lower() != "all":
+ print(f" 💻 Hardware Context Constraint: {args.device.upper()}")
+ filter_expression = f"({filter_expression}) and {args.device.lower()}"
+ else:
+ print(" 💻 Hardware Context Constraint: ALL AVAILABLE")
+
+ print("=" * 50)
+ print(f"COMPILED PYTEST KEYWORD EXPRESSION:\n 👉 {filter_expression}")
+ print("=" * 50 + "\n")
+
+ return filter_expression
+
+
+if __name__ == "__main__":
+ multiprocessing.set_start_method("spawn", force=True)
+ sorted_scenarios = get_available_scenarios()
+
+ parser = argparse.ArgumentParser(
+ description="Isolated HybridReader Telemetry Harness Suite"
+ )
+ parser.add_argument(
+ "-s",
+ "--source",
+ type=str,
+ # default="rtsp://172.17.0.1:8554/live1",
+ default="anduril_swarm_8K.mp4",
+ help="Video filename (located in /inputs) or RTSP target stream endpoint",
+ )
+ parser.add_argument(
+ "-d",
+ "--duration",
+ type=float,
+ default=2.0,
+ help="Test duration in minutes.",
+ )
+ parser.add_argument(
+ "--scenario",
+ nargs="+",
+ type=int,
+ choices=sorted_scenarios, # 1 - 4
+ default=None,
+ help=f"Specify one or more scenarios. Otherwise all scenarios are ran. Available scenarios: {sorted_scenarios}",
+ )
+
+ # MODEL TO USE
+ parser.add_argument(
+ "--no-custom",
+ action="store_false",
+ dest="custom_model_flag",
+ help="Enable if using Ultralytics YOLO model",
+ )
+ parser.add_argument(
+ "-m",
+ "--model",
+ type=str,
+ default="drone_detection",
+ dest="model_name",
+ help="Name of model. Required if `--no-custom` is enabled. [Default: drone_detection]",
+ )
+
+ # Filter tests
+ parser.add_argument(
+ "--type",
+ type=str,
+ choices=["object", "motion"],
+ dest="detection_type",
+ help="Filter by detection type (object or motion)",
+ )
+ parser.add_argument(
+ "--device",
+ type=str,
+ default="all",
+ choices=["cpu", "gpu", "all"],
+ help="Target hardware context selection filter.",
+ )
+ parser.add_argument(
+ "--sf",
+ action="store_true",
+ default=None,
+ dest="sf_enabled",
+ help="Filter by Smart Filtering",
+ )
+
+ # DEBUGGING
+ parser.add_argument(
+ "--debug",
+ action="store_true",
+ help="Enable debug message and save intermediate images for Smart Filtering tests",
+ )
+ # parser.add_argument(
+ # "-n",
+ # type=int,
+ # default=100,
+ # dest="debug_frame_limit",
+ # help="Number of frames used for debugging [Default: 100]",
+ # )
+ args = parser.parse_args()
+
+ os.environ["STREAM_SOURCE"] = args.source
+ os.environ["TEST_DURATION_MINS"] = str(args.duration)
+ os.environ["CUSTOM_MODEL_FLAG"] = "True" if args.custom_model_flag else "False"
+ os.environ["MODEL_NAME"] = args.model_name
+ os.environ["DEBUG"] = "1" if args.debug else "0"
+
+ # filter_expression = "test_scenario_4_detection_and_clipper"
+ # filter_expression = "test_scenario_3_video_clipper"
+ # filter_expression = "test_scenario_*_clipper"
+
+ filter_expression = get_pytest_filter_expression(args, sorted_scenarios)
+
+ pytest_args = [
+ "-k",
+ filter_expression,
+ "-s",
+ "-v",
+ "--log-cli-level=DEBUG",
+ __file__,
+ ]
+
+ print(
+ f"Launching decoupled testing suite configurations for destination targets: {args.source}",
+ flush=True,
+ )
+ sys.exit(pytest.main(pytest_args))
diff --git a/finetune/Dockerfile b/finetune/Dockerfile
index 0d16cc2..e093a7b 100644
--- a/finetune/Dockerfile
+++ b/finetune/Dockerfile
@@ -41,7 +41,13 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean
-RUN python3 -m venv ${VIRTUAL_ENV}
+ # hadolint ignore=DL3013
+RUN python3 -m venv ${VIRTUAL_ENV} && \
+ ${VIRTUAL_ENV}/bin/pip install --no-cache-dir \
+ "pip==26.0.1" \
+ "torch==2.10.0" \
+ "torchvision==0.25.0" \
+ "numpy==1.26.0"
ENV DEVICE="GPU"
@@ -59,13 +65,6 @@ RUN if [ "${DEVICE}" = "GPU" ]; then \
ENV PATH="$VIRTUAL_ENV/bin:/usr/local/cuda/bin:${PATH}"
ENV LD_LIBRARY_PATH="$VIRTUAL_ENV/lib:/usr/local/cuda/lib64:${LD_LIBRARY_PATH}"
-# RUN pip3 install --no-cache-dir "pip==26.0.1" "torch==2.10.0" "torchvision==0.25.0" "numpy==1.26.4"
-WORKDIR /home
-
-COPY requirements.* /home/
-# RUN pip3 install --no-cache-dir -r requirements.in
-RUN pip3 install --no-cache-dir --require-hashes -r requirements.txt
-
# ARG PYTHON_VERSION=3.10
ENV PYTHON_VERSION=3.10
@@ -125,11 +124,11 @@ RUN cmake -D CMAKE_BUILD_TYPE=RELEASE \
RUN rm -rf ${DEPENDENCY_DIR}
# Set the working directory in the container
-# WORKDIR /home
+WORKDIR /home
-# COPY requirements.* /home/
-# # RUN pip3 install --no-cache-dir -r requirements.in
-# RUN pip3 install --no-cache-dir --require-hashes -r requirements.txt
+COPY requirements.* /home/
+# RUN pip3 install --no-cache-dir -r requirements.in
+RUN pip3 install --no-cache-dir --require-hashes -r requirements.txt
RUN \
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 98427f2..347c739 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -3,19 +3,22 @@ FROM openvisualcloud/xeon-ubuntu2204-media-nginx:23.1@sha256:d19eb597dc210134063
ARG DEBUG="0"
ENV DEBUG="${DEBUG}"
+ARG DEBIAN_FRONTEND=noninteractive
# Prevent Python from writing .pyc files and enable unbuffered logging
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# fixes CVE-2023-4911 vulnerability on Ubuntu 22.04
+# hadolint ignore=DL3008
RUN apt-get update && \
- apt-get install --only-upgrade libc-bin libc6 && \
+ apt-get install -y --only-upgrade --no-install-recommends libc-bin libc6 && \
apt-get install -y -q --no-install-recommends python3-pip && \
rm -rf /var/lib/apt/lists/*
-COPY requirements.txt /home/requirements.txt
+COPY requirements.* /home/
RUN pip3 install --no-cache-dir --require-hashes -r /home/requirements.txt
+# RUN pip3 install --no-cache-dir -r /home/requirements.in
COPY *.py /home/
COPY *.conf /etc/nginx/
diff --git a/frontend/requirements.txt b/frontend/requirements.txt
index e80e09f..e9aeb70 100644
--- a/frontend/requirements.txt
+++ b/frontend/requirements.txt
@@ -115,9 +115,9 @@ charset-normalizer==3.4.4 \
--hash=sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3 \
--hash=sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e \
--hash=sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608
-idna==3.11 \
- --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \
- --hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
ply==3.11 \
--hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \
--hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce
@@ -180,9 +180,9 @@ tornado==6.5.5 \
--hash=sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521 \
--hash=sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7 \
--hash=sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5
-urllib3==2.6.3 \
- --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \
- --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4
+urllib3==1.26.20 \
+ --hash=sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e \
+ --hash=sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32
vdms==0.0.23 \
--hash=sha256:2dec153f8cb33f27cdb9ab33125198195fd29a6777cf890fc735774ed9327874 \
--hash=sha256:7cd93242df644947c009559f31b4e19eb1dab6b62ab783ce5a2a54a4ad392f57
diff --git a/frontend/setting.py b/frontend/setting.py
index 3ddf175..c39ff7e 100755
--- a/frontend/setting.py
+++ b/frontend/setting.py
@@ -1,9 +1,13 @@
#!/usr/bin/python3
+import json
+import os
from concurrent.futures import ThreadPoolExecutor
-from tornado import gen, web
-from tornado.concurrent import run_on_executor
+from tornado import gen, httpclient, web
+
+BACKEND_URL = os.getenv("BACKEND_URL", "http://fastapi-service:8000")
+FASTAPI_URL = f"{BACKEND_URL}/model_classes"
class SettingHandler(web.RequestHandler):
@@ -14,197 +18,148 @@ def __init__(self, app, request, **kwargs):
def check_origin(self, origin):
return True
- @run_on_executor
+ @gen.coroutine
+ def get_model_classes(self):
+ """Asynchronously fetches classes from the FastAPI container."""
+ client = httpclient.AsyncHTTPClient()
+ default_classes = ["class0"]
+
+ try:
+ response = yield client.fetch(FASTAPI_URL)
+ data = json.loads(response.body.decode("utf-8"))
+ return data.get("classes", default_classes) # Default fallback if empty
+ # with open(MODEL_CLASSES_FILE, "r") as f:
+ # data = json.load(f)
+ # return data.get("classes", default_classes)
+ except Exception as e:
+ # Fallback to a basic list if the model container is unreachable
+ print(f"Error fetching model classes: {e}")
+ return default_classes
+
+ # @run_on_executor
+ @gen.coroutine
def _settings(self):
- return {
- "controls": [
+ include_advanced = False
+ dynamic_objects = yield self.get_model_classes()
+
+ controls = []
+ if "person" in dynamic_objects:
+ person_control = {
+ "name": "person",
+ "icon": "images/person.png",
+ "description": "Find Person",
+ "params": [
+ {
+ "name": "Age Min",
+ "type": "number",
+ "value": 18,
+ },
+ {
+ "name": "Age Max",
+ "type": "number",
+ "value": 75,
+ },
+ {
+ "name": "Gender",
+ "type": "list",
+ "values": [
+ "skip",
+ "male",
+ "female",
+ ],
+ "value": "skip",
+ },
+ {
+ "name": "Emotion List",
+ "type": "list",
+ "values": [
+ "skip",
+ "neutral",
+ "happy",
+ "sad",
+ "surprise",
+ "anger",
+ ],
+ "value": "skip",
+ },
+ ],
+ }
+ controls.append(person_control)
+
+ object_control = {
+ "name": "object",
+ "icon": "images/object.png",
+ "description": "Find Object",
+ "params": [
{
- "name": "person",
- "icon": "images/person.png",
- "description": "Find Person",
- "params": [
- {
- "name": "Age Min",
- "type": "number",
- "value": 18,
- },
- {
- "name": "Age Max",
- "type": "number",
- "value": 75,
- },
- {
- "name": "Gender",
- "type": "list",
- "values": [
- "skip",
- "male",
- "female",
- ],
- "value": "skip",
- },
- {
- "name": "Emotion List",
- "type": "list",
- "values": [
- "skip",
- "neutral",
- "happy",
- "sad",
- "surprise",
- "anger",
- ],
- "value": "skip",
- },
- ],
+ "name": "Object List",
+ "type": "list",
+ "values": sorted(dynamic_objects),
+ "value": dynamic_objects[0],
},
{
- "name": "object",
- "icon": "images/object.png",
- "description": "Find Object",
- "params": [
- {
- "name": "Object List",
- "type": "list",
- "values": [
- "airplane",
- "apple",
- "backpack",
- "banana",
- "baseball bat",
- "baseball glove",
- "bear",
- "bed",
- "bench",
- "bicycle",
- "bird",
- "boat",
- "book",
- "bottle",
- "bowl",
- "broccoli",
- "bus",
- "cake",
- "car",
- "carrot",
- "cat",
- "cell phone",
- "chair",
- "clock",
- "couch",
- "cow",
- "cup",
- "dining table",
- "dog",
- "donut",
- "elephant",
- "fire hydrant",
- "fork",
- "frisbee",
- "giraffe",
- "hair drier",
- "handbag",
- "horse",
- "hot dog",
- "keyboard",
- "kite",
- "knife",
- "laptop",
- "microwave",
- "motorcycle",
- "mouse",
- "orange",
- "oven",
- "parking meter",
- "person",
- "pizza",
- "potted plant",
- "refrigerator",
- "remote",
- "sandwich",
- "scissors",
- "sheep",
- "sink",
- "skateboard",
- "skis",
- "snowboard",
- "spoon",
- "sports ball",
- "stop sign",
- "suitcase",
- "surfboard",
- "teddy bear",
- "tennis racket",
- "tie",
- "toaster",
- "toilet",
- "toothbrush",
- "traffic light",
- "train",
- "truck",
- "tv",
- "umbrella",
- "vase",
- "wine glass",
- "zebra",
- ],
- "value": "person",
- },
- {
- "name": "Object Count",
- "type": "list",
- "values": ["skip"] + [str(x) for x in range(1, 26)],
- "value": "skip",
- },
- {
- "name": "Object Count Condition",
- "type": "list",
- "values": ["==", "<=", "<", ">=", ">"],
- "value": "==",
- },
- {
- "name": "Frame ID",
- "type": "text",
- "value": "skip",
- },
- {
- "name": "Frame Condition",
- "type": "list",
- "values": [
- "==",
- "<=",
- ">=",
- ],
- "value": "==",
- },
- ],
+ "name": "Object Count",
+ "type": "list",
+ "values": ["skip"] + [str(x) for x in range(1, 26)],
+ "value": "skip",
+ },
+ {
+ "name": "Object Count Condition",
+ "type": "list",
+ "values": ["==", "<=", "<", ">=", ">"],
+ "value": "==",
+ },
+ {
+ "name": "Frame ID",
+ "type": "text",
+ "value": "skip",
},
{
- "name": "video",
- "icon": "images/video.png",
- "description": "Find Video",
- "params": [
- {
- "name": "Video Name",
- "type": "text",
- "value": "*",
- }
+ "name": "Frame Condition",
+ "type": "list",
+ "values": [
+ "==",
+ "<=",
+ ">=",
],
+ "value": "==",
},
- # {
- # "name": "advanced",
- # "icon": "images/advanced.png",
- # "description": "Advanced",
- # "params": [
- # {
- # "name": "Search Queries",
- # "type": "text",
- # "value": "",
- # }
- # ],
- # },
],
}
+ controls.append(object_control)
+
+ video_control = {
+ "name": "video",
+ "icon": "images/video.png",
+ "description": "Find Video",
+ "params": [
+ {
+ "name": "Video Name",
+ "type": "text",
+ "value": "*",
+ }
+ ],
+ }
+ controls.append(video_control)
+
+ if include_advanced:
+ advanced_control = {
+ "name": "advanced",
+ "icon": "images/advanced.png",
+ "description": "Advanced",
+ "params": [
+ {
+ "name": "Search Queries",
+ "type": "text",
+ "value": "",
+ }
+ ],
+ }
+ controls.append(advanced_control)
+
+ return {
+ "controls": controls,
+ }
@gen.coroutine
def get(self):
diff --git a/start_app.sh b/start_app.sh
index 3f03e56..a8b6a0a 100755
--- a/start_app.sh
+++ b/start_app.sh
@@ -3,7 +3,7 @@
# This script runs the Curation application
#######################################################################################################################
# DEFAULT VARIABLES
-INGESTION="object,face"
+INGESTION="object" #,face"
EXP_TYPE=compose
DEBUG="0"
DEVICE="CPU"
diff --git a/udf/Dockerfile b/udf/Dockerfile
index 8114b5c..32d3cc1 100644
--- a/udf/Dockerfile
+++ b/udf/Dockerfile
@@ -9,8 +9,9 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# fixes CVE-2023-4911 vulnerability on Ubuntu 22.04
+# hadolint ignore=DL3008
RUN apt-get update && \
- apt-get install --only-upgrade libc-bin libc6 && \
+ apt-get install -y --only-upgrade --no-install-recommends libc-bin libc6 && \
apt-get install -y -q --no-install-recommends python3-pip python3-venv \
curl libgl1-mesa-glx && rm -rf /var/lib/apt/lists/*
RUN python3 -m venv ${VIRTUAL_ENV}
diff --git a/udf/requirements.txt b/udf/requirements.txt
index ef85742..38d54e7 100644
--- a/udf/requirements.txt
+++ b/udf/requirements.txt
@@ -198,9 +198,9 @@ tomli==1.1.0 \
vdms==0.0.23 \
--hash=sha256:2dec153f8cb33f27cdb9ab33125198195fd29a6777cf890fc735774ed9327874 \
--hash=sha256:7cd93242df644947c009559f31b4e19eb1dab6b62ab783ce5a2a54a4ad392f57
-werkzeug==3.1.6 \
- --hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
- --hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
+werkzeug==3.1.8 \
+ --hash=sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50 \
+ --hash=sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44
wheel==0.46.3 \
--hash=sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d \
--hash=sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803
diff --git a/video/Dockerfile b/video/Dockerfile
index 4193004..ad5732d 100644
--- a/video/Dockerfile
+++ b/video/Dockerfile
@@ -1,59 +1,48 @@
-FROM openvisualcloud/xeon-ubuntu2204-media-nginx:23.1@sha256:d19eb597dc210134063803630ae2ea1ec84dfd4189138f59551e2f5ed047284a as build
+FROM openvisualcloud/xeon-ubuntu2204-media-nginx:23.1@sha256:d19eb597dc210134063803630ae2ea1ec84dfd4189138f59551e2f5ed047284a
ENV VIRTUAL_ENV=/opt/venv
ARG DEBIAN_FRONTEND=noninteractive
-# fixes CVE-2023-4911 vulnerability on Ubuntu 22.04
+# Prevent Python from writing .pyc files and enable unbuffered logging
+ENV PYTHONDONTWRITEBYTECODE=1 \
+ PYTHONUNBUFFERED=1
+
+# hadolint ignore=DL3008
RUN apt-get update && \
- apt-get install --only-upgrade libc-bin libc6 && \
+ apt-get install -y --only-upgrade --no-install-recommends libc-bin libc6 && \
apt-get install -y -q --no-install-recommends python3-setuptools \
python3-tornado python3-ply python3-pip python3-venv \
&& rm -rf /var/lib/apt/lists/*
+
RUN python3 -m venv ${VIRTUAL_ENV}
+
+# COPY --from=lcc_base_video_image:latest ${VIRTUAL_ENV} ${VIRTUAL_ENV}
+# COPY --from=lcc_base_video_image:latest /home /home
+
+# activate virtual environment
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
-COPY resources /home/resources
+# Set the working directory in the container
+WORKDIR /home
+
COPY requirements.* /home/
ARG DEVICE="CPU"
ENV DEVICE="${DEVICE}"
-RUN pip3 install --no-cache-dir --require-hashes -r /home/requirements.txt --index-url https://download.pytorch.org/whl/cpu --extra-index-url https://pypi.org/simple
-RUN if [ "${DEVICE}" = "CPU" ]; then \
- pip3 install --no-cache-dir --require-hashes -r /home/requirements.${DEVICE}.txt --index-url https://download.pytorch.org/whl/cpu --extra-index-url https://pypi.org/simple; \
- else \
- pip3 install --no-cache-dir --require-hashes -r /home/requirements.${DEVICE}.txt ; \
- fi;
-
-RUN omz_downloader --list /home/resources/models/models.lst -o /home/resources/models --precisions FP16
+# RUN pip3 install --no-cache-dir -r /home/requirements.in
+RUN pip3 install --no-cache-dir --require-hashes -r /home/requirements.txt
-# FINAL IMAGE
-FROM openvisualcloud/xeon-ubuntu2204-media-nginx:23.1@sha256:d19eb597dc210134063803630ae2ea1ec84dfd4189138f59551e2f5ed047284a
-ENV VIRTUAL_ENV=/opt/venv
-ARG DEBIAN_FRONTEND=noninteractive
-
-# Prevent Python from writing .pyc files and enable unbuffered logging
-ENV PYTHONDONTWRITEBYTECODE=1 \
- PYTHONUNBUFFERED=1
-
-RUN apt-get update && \
- apt-get install --only-upgrade libc-bin libc6 && \
- apt-get install -y -q --no-install-recommends python3-tornado \
- curl libgl1-mesa-glx && rm -rf /var/lib/apt/lists/*
-ENV PATH="$VIRTUAL_ENV/bin:$PATH"
-
-# COPY --from=build /usr/local/lib/python3.10 /usr/local/lib/python3.10
-COPY --from=build ${VIRTUAL_ENV} ${VIRTUAL_ENV}
-COPY --from=build /home /home
-
-
-WORKDIR /home
COPY *.py /home/
+COPY *.yaml /home/
+COPY *.conf /etc/nginx/
COPY cleanup.sh /home/
COPY manage.sh /home/
-COPY *.conf /etc/nginx/
-CMD ["/bin/bash","-c","python3 /home/resources/models/download_yolo.py && /home/manage.sh"]
+# COPY frontend /home/frontend
+# COPY include /home/include
+# CMD ["/bin/bash","-c","python /home/resources/models/download_yolo.py && /home/manage.sh"]
+CMD ["/bin/bash","-c","/home/manage.sh"]
####
ARG USER
@@ -66,7 +55,7 @@ RUN if [ ${GID} -gt 0 ]; then groupadd -f -g ${GID} ${GROUP}; fi; \
if [ ${UID} -gt 0 ]; then useradd --no-log-init -d /home/${USER} -g ${GID} -K UID_MAX=${UID} -K UID_MIN=${UID} ${USER}; fi; \
touch /var/run/nginx.pid && \
mkdir -p /var/log/nginx /var/lib/nginx /var/www/cache /var/www/gen /var/www/mp4 && \
- chown -R ${UID}.${GID} /var/run/nginx.pid /var/log/nginx /var/lib/nginx /var/www /etc/nginx/nginx.conf /home/resources
+ chown -R ${UID}.${GID} /var/run/nginx.pid /var/log/nginx /var/lib/nginx /var/www /etc/nginx/nginx.conf /home
VOLUME ["/var/www"]
USER ${UID}
####
diff --git a/video/manage.sh b/video/manage.sh
index ac4247d..9d1ffc1 100755
--- a/video/manage.sh
+++ b/video/manage.sh
@@ -1,7 +1,8 @@
#!/bin/bash -e
# Watch directory
-python3 /home/watch_and_send2vdms.py ${WATCH_DIR} &
+echo "WATCH_DIR: ${WATCH_DIR}"
+python3 /home/source_watcher.py ${WATCH_DIR} &
# run tornado
-exec /home/manage.py
+exec ${VIRTUAL_ENV}/bin/python /home/manage.py
diff --git a/video/nginx.conf b/video/nginx.conf
index bfc5853..357d154 100644
--- a/video/nginx.conf
+++ b/video/nginx.conf
@@ -8,6 +8,19 @@ events {
}
http {
+
+ # Include MIME types
+ include mime.types;
+ default_type application/octet-stream;
+
+ # Basic logging
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+ error_log /var/log/nginx/error.log warn;
+
# `proxy_read_timeout` maximum time Nginx will wait for a response from a proxied server.
# If the proxied server does not send any data within this time, Nginx will close the connection.
# Default: 60
@@ -22,15 +35,16 @@ http {
# Default: 60
# proxy_connect_timeout 300s;
- include mime.types;
- default_type application/octet-stream;
+ # include mime.types;
+ # default_type application/octet-stream;
server {
listen 8080;
server_name _;
# client_body_timeout 10s;
# client_header_timeout 10s;
- client_max_body_size 1024M;
+ # client_max_body_size 1024M;
+ client_max_body_size 2G; # 8K frames are huge
sendfile on;
location /api/ {
@@ -46,5 +60,21 @@ http {
internal;
root /var/www;
}
+
+ # location / {
+ # # proxy_http_version 1.1;
+
+ # # Mandatory for WebSocket support
+ # proxy_set_header Upgrade $http_upgrade;
+ # proxy_set_header Connection "upgrade";
+
+ # # Standard proxy headers
+ # proxy_set_header Host $host;
+ # proxy_set_header X-Real-IP $remote_addr;
+ # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # proxy_set_header X-Forwarded-Proto $scheme;
+ # proxy_read_timeout 86400;
+ # }
}
}
+
diff --git a/video/process_stream.py b/video/process_stream.py
deleted file mode 100644
index eca85bf..0000000
--- a/video/process_stream.py
+++ /dev/null
@@ -1,988 +0,0 @@
-import gc
-import multiprocessing as mp
-import os
-import queue
-import shlex
-import subprocess
-import sys
-import time # time library
-import traceback
-from concurrent.futures import ThreadPoolExecutor, as_completed
-from datetime import datetime
-from pathlib import Path
-from random import randint
-
-import cv2 # OpenCV library
-from openvino.runtime import Core
-from ultralytics import YOLO
-from ultralytics.utils.checks import check_imgsz
-from utils import str2bool
-
-import vdms
-
-
-def _sort_dict_by_frame(in_dict):
- def _by_int(key):
- return tuple(int(k) for k in key.split("_"))
-
- return dict(sorted(in_dict.items(), key=lambda x: _by_int(x[0])))
-
-
-""" GENERAL VARIABLES """
-CODE_DIR = os.getenv("CODE_DIR", "/home")
-CUSTOM_MODEL_FLAG = str2bool(os.getenv("CUSTOM_MODEL_FLAG", False))
-DBHOST = os.getenv("DBHOST", "vdms-service")
-DEBUG = os.getenv("DEBUG", "0")
-DEVICE = os.getenv("DEVICE", "CPU")
-INGESTION = os.getenv("INGESTION", "object,face")
-RESIZE_FLAG = str2bool(os.getenv("RESIZE_FLAG", False))
-OMIT_DETECTIONS_FLAG = str2bool(os.getenv("OMIT_DETECTIONS_FLAG", False))
-SHARED_OUTPUT = os.getenv("SHARED_OUTPUT", "/var/www/mp4")
-Path(SHARED_OUTPUT).mkdir(parents=True, exist_ok=True)
-TEST_MODE = str2bool(os.getenv("TEST_FLAG", False))
-TMP_LOCATION = os.getenv("TMP_LOCATION", "/var/www/cache/")
-UDF_HOST = os.getenv("UDF_HOST", "video-service")
-MODEL_NAME = os.getenv("MODEL_NAME", "yolo11n")
-
-LOCKTIMEOUT_RETRIES = 5
-ERR_KEYWORDS = [
- "timeout",
- "null search iterator",
- "outoftransactions",
- "internal server",
-]
-
-BATCH_SIZE = 1
-DBPORT = 55555
-DEBUG_FLAG = True if DEBUG == "1" else False
-DETECTION_THRESHOLD = 0.25
-DEVICE_OV = "AUTO"
-HALF_FLAG = True
-IOU_THRESHOLD = 0.7
-MODEL_PRECISION = "FP16"
-MODEL_W, MODEL_H = (640, 640)
-TARGET_FPS = 15
-UDF_PORT = 5011
-WRITER_FOURCC = cv2.VideoWriter_fourcc(*"mp4v") # avc1, mp4v
-NUM_USUABLE_CPUS = 2 # os.cpu_count()
-
-if CUSTOM_MODEL_FLAG:
- model_path = f"{CODE_DIR}/resources/models/ultralytics/custom_models/{MODEL_NAME}"
-else:
- model_path = f"{CODE_DIR}/resources/models/ultralytics/{MODEL_NAME}/{MODEL_PRECISION}/{MODEL_NAME}"
-
-if not TEST_MODE:
- db = vdms.vdms()
- db.connect(DBHOST, DBPORT)
-
-if DEVICE == "GPU":
- model_path += ".engine"
- batch_size = int(os.environ.get("GPU_BATCH_SIZE", 1))
- os.environ["CUDA_VISIBLE_DEVICES"] = "0"
- from torch.cuda import empty_cache
-else:
- model_path += "_openvino_model/"
- batch_size = int(os.environ.get("CPU_BATCH_SIZE", 1)) # 8
-
-device_input = DEVICE.lower() if DEVICE == "CPU" else "cuda"
-
-all_metadata = {}
-send_metadata_queue = mp.Queue()
-
-
-def retry_query(query, num_retries: int = LOCKTIMEOUT_RETRIES, sleep_timer: int = 0):
- global db
- for ridx in range(num_retries + 1):
- response, _ = db.query(query, [[]])
- if "FailedCommand" in response[0] and any(
- k in response[0]["info"].lower() for k in ERR_KEYWORDS
- ):
- err = response[0]["info"]
- if DEBUG == "1":
- query_type = list(query[0].keys())[0]
- print(
- f"DEBUG [process_stream Attempt #{ridx}] Received '{err}' for {query_type} query",
- flush=True,
- )
- if sleep_timer > 0:
- time.sleep(sleep_timer)
- else:
- if DEBUG == "1":
- print(
- f"[DEBUG process_stream] Successful query response: {response}",
- flush=True,
- )
- break # Continue
- return response
-
-
-def load_models():
- # OBJECT DETECTION
- model = YOLO(model_path, verbose=False, task="detect")
-
- # FACE, AGE, GENDER, AND EMOTIONS
- ie = Core()
- face_detection_model_xml = f"{CODE_DIR}/resources/models/intel/face-detection-adas-0001/{MODEL_PRECISION}/face-detection-adas-0001.xml"
- face_detection_model = ie.read_model(
- model=face_detection_model_xml,
- weights=face_detection_model_xml.replace(".xml", ".bin"),
- )
- # face_det_w, face_det_h = 672, 384
- _, face_det_c, face_det_h, face_det_w = face_detection_model.inputs[0].shape
- face_det_compiled_model = ie.compile_model(face_detection_model, DEVICE_OV)
-
- age_gender_classification_model_xml = f"{CODE_DIR}/resources/models/intel/age-gender-recognition-retail-0013/{MODEL_PRECISION}/age-gender-recognition-retail-0013.xml"
- age_gender_classification_model = ie.read_model(
- model=age_gender_classification_model_xml,
- weights=age_gender_classification_model_xml.replace(".xml", ".bin"),
- )
- # ag_w, ag_h = 62, 62
- _, ag_c, ag_h, ag_w = age_gender_classification_model.inputs[0].shape
- ag_compiled_model = ie.compile_model(age_gender_classification_model, DEVICE_OV)
-
- emotions_classification_model_xml = f"{CODE_DIR}/resources/models/intel/emotions-recognition-retail-0003/{MODEL_PRECISION}/emotions-recognition-retail-0003.xml"
- emotions_classification_model = ie.read_model(
- model=emotions_classification_model_xml,
- weights=emotions_classification_model_xml.replace(".xml", ".bin"),
- )
- # em_w, em_h = 64, 64
- _, em_c, em_h, em_w = emotions_classification_model.inputs[0].shape
- em_compiled_model = ie.compile_model(emotions_classification_model, DEVICE_OV)
-
- return (
- model,
- [face_det_compiled_model, ag_compiled_model, em_compiled_model],
- [face_det_c, face_det_h, face_det_w],
- [ag_c, ag_h, ag_w],
- [em_c, em_h, em_w],
- )
-
-
-model, face_models, face_det_CHW, ag_CHW, em_CHW = load_models()
-
-
-""" DETECTION FUNCTIONS """
-
-
-# Extract metadata from object model results
-def extract_metadata_from_results(
- stream_name, frameNum, results, img_size, fps=TARGET_FPS
-):
- fW, fH = img_size
- metadata = dict()
- try:
- for _, result in enumerate(results):
- # GET METADATA FOR CLIP
- boxes = result.boxes.cpu()
- oidx = 0
- for box in boxes:
- confidence = float(box.conf.item())
- if confidence > DETECTION_THRESHOLD:
- class_id = int(box.cls.item())
- class_name = str(result.names[class_id])
-
- if not OMIT_DETECTIONS_FLAG:
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
- print(
- # f"[OBJECT DETECTION] {class_name} detected in frame {frameNum} (Total detected: {current_cnt})",
- f"[{timestamp}] {stream_name} DETECTION on Frame {frameNum}: {class_name} detected",
- flush=True,
- )
- x1, y1, x2, y2 = box.xyxy.tolist()[0]
- height = min(y2, fH) - max(0, y1)
- width = min(x2, fW) - max(0, x1)
- object_res = [
- x1,
- y1,
- height,
- width,
- result.names[class_id],
- confidence,
- fH,
- fW,
- ]
-
- framenum_str = f"{frameNum:04d}_{oidx:04d}"
- if DEBUG_FLAG:
- meta_str = ",".join(
- [str(o) for o in object_res + [framenum_str]]
- )
- print(f"[{stream_name} METADATA],{meta_str}", flush=True)
-
- metadata[framenum_str] = {
- "frameId": frameNum,
- "bbId": framenum_str,
- "bbox": {
- "x": int(object_res[0]),
- "y": int(object_res[1]),
- "height": int(object_res[2]),
- "width": int(object_res[3]),
- "object": str(object_res[4]),
- "object_det": {
- "confidence": float(object_res[5]),
- "frameH": int(fH),
- "frameW": int(fW),
- },
- },
- }
- oidx += 1
-
- except Exception:
- e = traceback.format_exc()
- print(f"Error in {stream_name} extract_metadata_from_results: {e}", flush=True)
-
- return metadata
-
-
-# Detect faces from frame
-def face_detection(
- stream_name, frameNum, frame, img_size, face_models, face_det_CHW, ag_CHW, em_CHW
-):
- face_det_compiled_model, ag_compiled_model, em_compiled_model = face_models
- face_det_c, face_det_h, face_det_w = face_det_CHW
- ag_c, ag_h, ag_w = ag_CHW
- em_c, em_h, em_w = em_CHW
- W, H = img_size
- bs = 1
-
- genders = ["female", "male"]
- emotions = ["neutral", "happy", "sad", "surprise", "anger"]
-
- # Resize expects HWC
- input_image = cv2.resize(
- frame, (face_det_w, face_det_h), interpolation=cv2.INTER_AREA
- )
- input_image = input_image.transpose(2, 0, 1) # Shape: CHW
- input_image = input_image.reshape((bs, face_det_c, face_det_h, face_det_w))
-
- output_layer = face_det_compiled_model.output(0)
- result = face_det_compiled_model([input_image])[output_layer]
-
- # Process the detections
- faces = []
- metadata = dict()
- oidx = 1
- for detection in result[0][0]:
- confidence = float(detection[2])
- if confidence > DETECTION_THRESHOLD:
- # Draw a bounding box around the face
- x1 = int(detection[3] * frame.shape[1])
- if x1 < 0:
- x1 = 0
-
- y1 = int(detection[4] * frame.shape[0])
- if y1 < 0:
- y1 = 0
-
- x2 = int(detection[5] * frame.shape[1])
- if x2 > frame.shape[1] - 1:
- x2 = frame.shape[1] - 1
-
- y2 = int(detection[6] * frame.shape[0])
- if y2 > frame.shape[0] - 1:
- y2 = frame.shape[0] - 1
-
- height = y2 - y1
- width = x2 - x1
-
- face_roi = frame[y1:y2, x1:x2]
- # print(face_roi.shape)
- age = gender = emotion = None
- try:
- ag_face_blob = cv2.resize(
- face_roi, (ag_w, ag_h), interpolation=cv2.INTER_AREA
- )
- ag_face_blob = ag_face_blob.transpose((2, 0, 1))
- ag_face_blob = ag_face_blob.reshape((bs, ag_c, ag_h, ag_w))
- ag_result = ag_compiled_model([ag_face_blob])
- age = int(ag_result["fc3_a"].flatten()[0] * 100)
- gender = str(genders[ag_result["prob"].argmax()])
- except Exception as e:
- print(f"Error occurred: {e}. Skipping age-gender model", flush=True)
-
- try:
- em_face_blob = cv2.resize(
- face_roi, (em_w, em_h), interpolation=cv2.INTER_AREA
- )
- em_face_blob = em_face_blob.transpose((2, 0, 1))
- em_face_blob = em_face_blob.reshape((bs, em_c, em_h, em_w))
- em_result = em_compiled_model([em_face_blob])[
- em_compiled_model.output(0)
- ]
- emotion = str(emotions[em_result.argmax()])
- except Exception as e:
- print(f"Error occurred: {e}. Skipping emotion model", flush=True)
- face_res = [x1, y1, height, width, age, gender, emotion, confidence, H, W]
- # print(face_res)
- faces.append(face_res)
-
- tdict = {
- "x": int(face_res[0]),
- "y": int(face_res[1]),
- "height": int(face_res[2]),
- "width": int(face_res[3]),
- "object": "face",
- "object_det": {
- "age": int(face_res[4]),
- "gender": str(face_res[5]),
- "emotion": str(face_res[6]),
- "confidence": float(face_res[7]),
- "frameH": int(H),
- "frameW": int(W),
- },
- }
-
- framenum_str = f"{frameNum:04d}_{oidx:04d}"
- if DEBUG_FLAG:
- meta_str = ",".join([str(o) for o in face_res + [framenum_str]])
- print(f"[{stream_name} METADATA],{meta_str}", flush=True)
-
- metadata[framenum_str] = {
- "frameId": frameNum,
- "bbId": framenum_str,
- "bbox": tdict,
- }
- oidx += 1
-
- return metadata
-
-
-# Inference Function
-def infer_worker(
- stream_name,
- frameNum,
- frame,
- img_size,
- INGESTION,
- fps=TARGET_FPS,
-): # img_size:(W,H)
- global model, face_models, face_det_CHW, ag_CHW, em_CHW
-
- height, width = frame.shape[:2]
- if (width, height) != img_size:
- frame = cv2.resize(frame, img_size)
-
- metadata = {}
- metadata_face = {}
- try:
- if "object" in INGESTION:
- results = model.predict(
- frame,
- imgsz=(img_size[1], img_size[0]),
- batch=BATCH_SIZE,
- conf=DETECTION_THRESHOLD,
- iou=IOU_THRESHOLD,
- half=HALF_FLAG,
- device=device_input,
- verbose=False,
- stream=True,
- )
-
- metadata = extract_metadata_from_results(
- stream_name, frameNum, results, img_size, fps=fps
- )
- del results
-
- if "face" in INGESTION:
- metadata_face = face_detection(
- stream_name,
- frameNum,
- frame,
- img_size,
- face_models,
- face_det_CHW,
- ag_CHW,
- em_CHW,
- )
-
- if DEVICE == "GPU":
- empty_cache() # Frees memory no longer used
- gc.collect() # Forces garbage collector
- except Exception:
- e = traceback.format_exc()
- print(f"Error in {stream_name} infer_worker: {e}", flush=True)
- return metadata, metadata_face
-
-
-""" HELPFUL FUNCTIONS """
-
-
-# Manual FPS calculation if OpenCV reports 0
-def manual_fps_calculation(src, num_frames=10):
- vid_obj = cv2.VideoCapture(src)
-
- frame_count = 0
- start_t = time.time()
-
- while frame_count < num_frames:
- grabbed, frame = vid_obj.read()
-
- if not grabbed:
- break
-
- frame_count += 1
-
- end_t = time.time()
- vid_obj.release()
-
- elapsed_t = end_t - start_t
-
- if elapsed_t > 0:
- return frame_count / elapsed_t
- else:
- return 0
-
-
-# Generate and run UDF query
-def get_udf_query(
- filename_path,
- properties,
- ingest_mode,
- new_size,
- id="udf_metadata",
- metadata=None,
- test_mode=TEST_MODE,
-):
- query = {
- "AddVideo": {
- "from_file_path": str(filename_path), # from_server_file
- "is_local_file": True,
- "properties": properties,
- "operations": [
- {
- "type": "syncremoteOp", # "remoteOp",
- "url": f"http://{UDF_HOST}:{UDF_PORT}/video",
- "options": {
- "id": id,
- "otype": ingest_mode,
- "media_type": "video",
- "input_sizeWH": new_size,
- "filename": properties["Name"],
- "ingestion": 1,
- },
- }
- ],
- }
- }
-
- if id == "udf_metadata" and metadata is not None:
- query["AddVideo"]["operations"][0]["options"]["metadata"] = metadata
-
- if test_mode:
- return
-
- filename = str(Path(filename_path).name)
- if DEBUG_FLAG:
- print(
- f"[TIMING],start_udf_ingest_{ingest_mode},{filename}," + str(time.time()),
- flush=True,
- )
- try:
- res = retry_query([query], sleep_timer=randint(1, 5))
-
- if DEBUG_FLAG:
- print(
- f"[TIMING],end_udf_ingest_{ingest_mode},{filename}," + str(time.time()),
- flush=True,
- )
- print(f"[DEBUG] {filename} PROPERTIES: {properties}", flush=True)
- print(f"[DEBUG] {filename} INGEST_VIDEO RESPONSE: {res}", flush=True)
- except Exception:
- e = traceback.format_exc()
- print(f"[DEBUG] VDMS Query Exception: {e}", flush=True)
-
- # elapsed_time = time.time() - start_t
-
- # db.disconnect()
- # del db
-
-
-# Release Video Writer object and re-encode video to seek via ffmpeg later
-def release_clip_and_reencode(clip_key, _out_vid, clip_filename, tmp_file, target_fps):
- if DEBUG == "1":
- print(
- f"[TIMING],start_release_clip,{clip_key},{time.time()}",
- flush=True,
- )
- _out_vid.release()
- if DEBUG == "1":
- print(
- f"[TIMING],end_release_clip,{clip_key},{time.time()}",
- flush=True,
- )
- _out_vid = None
-
- # Re-encode video in order to seek via ffmpeg later
- GENERAL_OPTS = "-flags -global_header -hide_banner -loglevel error -nostats -tune zerolatency -flush_packets 0" # -filter:v fps={target_fps}
- CONVERSION = f"-c:v libx264 -preset ultrafast -filter:v fps=fps={target_fps}" # "-c:v libx264 -preset medium"
- reencode_cmd = f"ffmpeg -y -i {tmp_file} {GENERAL_OPTS} {CONVERSION} -crf 23 -c:a copy {clip_filename}"
- cmd_list = shlex.split(reencode_cmd)
- if DEBUG == "1":
- print(
- f"[TIMING],start_reencode,{clip_key},{time.time()}",
- flush=True,
- )
- subprocess.run(cmd_list, check=True)
- end_time = time.time()
- # filename = str(Path(clip_filename).name)
- if DEBUG == "1":
- print(
- f"[TIMING],end_reencode,{clip_key},{end_time}",
- flush=True,
- )
- print(f"[TIMING],Save clip,{clip_key},{end_time}", flush=True)
- os.remove(tmp_file)
- return _out_vid
-
-
-# method to send metadata to VDMS once clip is saved
-def metadata2vdms(
- clip_key,
- clip_filename,
- clip_metadata,
- width,
- height,
-):
- if DEBUG == "1":
- print(
- f"[TIMING],start_clip_metadata,{clip_key},{time.time()}",
- flush=True,
- )
-
- # Send metadata to UDF
- properties = {
- "Name": clip_key, # .split("/")[-1],
- "category": "video_path_rop",
- }
-
- combined_metadata = clip_metadata["object"] if "object" in clip_metadata else {}
- if "face" in clip_metadata:
- for face_frameidx_bbidx, value in clip_metadata["face"].items():
- face_frameidx, face_bbidx = face_frameidx_bbidx.split("_")
- max_obj_idx = 0
- for obj_frameidx_bbidx in combined_metadata:
- if face_frameidx in obj_frameidx_bbidx:
- _, obj_bbidx_ = obj_frameidx_bbidx.split("_")
- max_obj_idx = max(max_obj_idx, int(obj_bbidx_))
-
- if max_obj_idx > 0:
- new_face_bbidx = max_obj_idx + 1
- new_key = f"{face_frameidx}_{new_face_bbidx:04d}"
- combined_metadata[new_key] = value
- combined_metadata[new_key]["bbId"] = new_key
- else:
- combined_metadata[face_frameidx_bbidx] = value
-
- combined_metadata = _sort_dict_by_frame(combined_metadata)
- get_udf_query(
- clip_filename,
- properties,
- INGESTION.replace(",", "+"),
- (width, height),
- id="udf_metadata",
- metadata=combined_metadata,
- test_mode=TEST_MODE,
- )
-
- if DEBUG == "1":
- print(
- f"[TIMING],end_clip_metadata,{clip_key},{time.time()}",
- flush=True,
- )
-
-
-""" CLASSES """
-
-
-# method to save clip
-def save_clip(
- clip_filename, clip_id, tmp_file, _out_vid, frame_count, frameNum, target_fps
-):
- clip_key = Path(clip_filename).name
- if DEBUG == "1":
- print(
- f"[DEBUG] Clip {clip_key} (clip_id: {clip_id}) contains {frame_count} frames",
- flush=True,
- )
- _out_vid = release_clip_and_reencode(
- clip_key,
- _out_vid,
- clip_filename,
- tmp_file,
- target_fps,
- )
- if DEBUG == "1":
- print(
- f"[TIMING],end_get_clips,{clip_key},{time.time()}",
- flush=True,
- )
- return _out_vid
-
-
-# method to create clips (read frame write to file; add name to list)
-def send_metadata():
- global all_metadata
- clip_filename = ""
- clip_key = ""
- width = 0
- height = 0
- while True:
- try:
- queue_details = send_metadata_queue.get()
- if queue_details is None:
- break
-
- (clip_key, clip_filename, width, height) = queue_details
-
- metadata2vdms(
- clip_key,
- clip_filename,
- all_metadata[clip_key],
- width,
- height,
- )
- del all_metadata[clip_key]
-
- except queue.Empty:
- pass
-
-
-# defining a helper class for implementing multi-threading
-class VideoStream:
- # initialization method
- def __init__(self, src, fps=TARGET_FPS, fourcc=WRITER_FOURCC, camera_name=None):
- self.stream_id = src
- self.fourcc = fourcc
-
- if "://" in str(self.stream_id):
- if camera_name is not None:
- self.stream_name = camera_name
- else:
- self.stream_name = str(self.stream_id).split("/")[-1]
- else:
- self.stream_name = Path(self.stream_id).stem
-
- if self.stream_id.startswith("rtsp"):
- os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = "rtsp_transport;tcp"
-
- # Check that object is opened successfully
- self.connect_to_stream(time_limit_mins=5)
- if DEBUG == "1":
- print(
- f"[TIMING],Start processing,{self.stream_name},{time.time()}",
- flush=True,
- )
-
- self.setup_stream(fps)
-
- # Create ThreadPoolExecutor
- self.executor = ThreadPoolExecutor(max_workers=NUM_USUABLE_CPUS)
-
- # method to start thread
- def start(self):
- self.stopped = False
- self.t = []
- self.t.append(
- self.executor.submit(
- self.get_frames,
- )
- )
- self.t.append(
- self.executor.submit(
- send_metadata,
- )
- )
-
- # method to stop reading frames
- def stop(self):
- for t in as_completed(self.t):
- try:
- _ = t.result()
- except Exception as t_e:
- print(f"[DEBUG] Exception occurred in thread: {t_e}")
-
- # self.stopped = True
- self.video_obj.release()
-
- # method to open stream/video within 5min (default) limit
- def connect_to_stream(self, time_limit_mins=5):
- # opening video capture stream
- self.video_obj = cv2.VideoCapture(self.stream_id, cv2.CAP_FFMPEG)
-
- stream_available = False
- time_limit_secs = time_limit_mins * 60
- connect_time = time.time()
- while not stream_available:
- if self.video_obj.isOpened():
- stream_available = True
- elif self.stream_id.startswith("rtsp"):
- if time.time() - connect_time < time_limit_secs:
- self.video_obj = cv2.VideoCapture(self.stream_id, cv2.CAP_FFMPEG)
- else:
- print(
- f"Exceeds {time_limit_mins} mins limit to connect to {self.stream_name}. Exiting ..."
- )
- exit(1)
-
- # Gets video fps and framecount
- def get_fps_and_framecnt(self, fps):
- self.input_fps = int(self.video_obj.get(cv2.CAP_PROP_FPS)) # hardware fps
- if self.input_fps == 0: # Case when FPs isn't available
- self.input_fps = manual_fps_calculation(self.stream_id, num_frames=10)
-
- self.target_fps = fps if self.input_fps > fps else self.input_fps
- self.frame_skip = int(self.input_fps / self.target_fps)
- if self.frame_skip < 1:
- self.frame_skip = 1
-
- print(f"FPS of {self.stream_name} input stream: {self.input_fps}", flush=True)
- print(f"FPS of {self.stream_name} output mp4: {self.target_fps}", flush=True)
-
- # Frame count for videos
- self.frame_count = None
- if "://" not in str(self.stream_id):
- self.frame_count = int(self.video_obj.get(cv2.CAP_PROP_FRAME_COUNT))
-
- # Gets frame W and H details
- def get_frameWH(self):
- input_width = int(self.video_obj.get(cv2.CAP_PROP_FRAME_WIDTH))
- input_height = int(self.video_obj.get(cv2.CAP_PROP_FRAME_HEIGHT))
-
- if RESIZE_FLAG or ((input_height * input_width) < (MODEL_H * MODEL_W)):
- new_sizeHW = check_imgsz([MODEL_H, MODEL_W]) # expects hxw
- else:
- new_sizeHW = check_imgsz([input_height, input_width]) # expects hxw
-
- new_sizeWH = (new_sizeHW[1], new_sizeHW[0])
-
- self.width = new_sizeWH[0]
- self.height = new_sizeWH[1]
-
- # Sets up important info for stream
- def setup_stream(self, fps):
- self.inference_queue = mp.Queue()
- self.retrieved_frames = 0
- self.num_frames_processed = 0
-
- self.get_fps_and_framecnt(fps)
-
- self.get_frameWH()
-
- self._out_vid = None
- self.clip_end_frame = {}
- self.clip_filename = ""
- self.clip_frame_count = 0
- self.clip_frame_inds = []
- self.clip_id = 0
- self.clip_length_in_secs = 10
- self.clip_total_frames = int(float(self.clip_length_in_secs * self.target_fps))
-
- # method to process a frame
- def get_frames(self):
- clip_frame_idx = 0
- clip_id = 0
- if DEBUG == "1":
- print(
- f"[TIMING],start_get_frames,{self.stream_name},{time.time()}",
- flush=True,
- )
- while True:
- grabbed, frame = self.video_obj.read() # Read next frame
-
- if not grabbed: # or self.stopped:
- # self.stopped = True
- self.inference_queue.put(None)
- break
-
- frameNum = int(self.video_obj.get(cv2.CAP_PROP_POS_FRAMES))
- skip_frame_num = (frameNum - 1) % self.frame_skip
-
- if clip_frame_idx % self.clip_total_frames == 0:
- if "://" not in str(self.stream_id):
- clip_filename = f"{SHARED_OUTPUT}/{self.stream_name}_{clip_id}.mp4"
- else:
- clip_filename = (
- f"{SHARED_OUTPUT}/{self.stream_name}_{time.time()}.mp4"
- )
-
- tmp_file = TMP_LOCATION + clip_filename.split("/")[-1]
-
- if skip_frame_num == 0:
- h, w = frame.shape[:2]
- if (w, h) != (self.width, self.height):
- frame = cv2.resize(frame, (self.width, self.height))
-
- queue_details = (
- frameNum, # Overall frame number
- (clip_frame_idx % self.clip_total_frames)
- + 1, # Frame index in clip [1-indexed like opencv]
- clip_id, # Clip number
- clip_filename,
- tmp_file,
- frame.copy(), # Frame
- )
-
- self.inference_queue.put(queue_details)
- self.retrieved_frames += 1
-
- clip_frame_idx += 1
- if clip_frame_idx % self.clip_total_frames == 0:
- clip_id += 1
-
- if DEBUG == "1":
- print(
- f"[TIMING],end_get_frames,{self.stream_name},{time.time()}", flush=True
- )
-
-
-def process_stream(camera_src, camera_name=None):
- global all_metadata
- webcam_stream = VideoStream(str(camera_src), camera_name=camera_name)
- if DEBUG == "1":
- print(
- f"[TIMING],Start processing,{webcam_stream.stream_name},{time.time()}",
- flush=True,
- )
-
- start = time.time()
- # Start retrieving frames and add to queue
- webcam_stream.start()
- _out_vid = None
- clip_filename = ""
- clip_frame_idx = 0
- clip_id = 0
- clip_key = ""
- frameNum = 0
- tmp_file = ""
- while True:
- queue_details = webcam_stream.inference_queue.get()
-
- if queue_details is None:
- if _out_vid is not None:
- frame_count = clip_frame_idx
- if frame_count > TARGET_FPS:
- _out_vid = save_clip(
- clip_filename,
- clip_id,
- tmp_file,
- _out_vid,
- frame_count,
- frameNum,
- webcam_stream.target_fps,
- )
- send_metadata_queue.put(
- (
- clip_key,
- clip_filename,
- webcam_stream.width,
- webcam_stream.height,
- )
- )
- else:
- _out_vid = None
- send_metadata_queue.put(None)
- print("End of stream")
- break
-
- frameNum, clip_frame_idx, clip_id, clip_filename, tmp_file, frame = (
- queue_details
- )
- clip_key = Path(clip_filename).name
- if DEBUG == "1":
- print(
- f"[TIMING],start_infer_worker,{clip_key}-{clip_frame_idx},{time.time()}",
- flush=True,
- )
-
- metadata, metadata_face = infer_worker(
- webcam_stream.stream_name,
- clip_frame_idx,
- frame,
- (webcam_stream.width, webcam_stream.height), # img_size,
- INGESTION,
- fps=webcam_stream.target_fps,
- )
-
- if DEBUG == "1":
- print(
- f"[TIMING],end_infer_worker,{clip_key}-{clip_frame_idx},{time.time()}",
- flush=True,
- )
-
- all_metadata.setdefault(clip_key, {})
- all_metadata[clip_key].setdefault("object", {})
- all_metadata[clip_key]["object"].update(metadata)
- all_metadata[clip_key].setdefault("face", {})
- all_metadata[clip_key]["face"].update(metadata_face)
- webcam_stream.num_frames_processed += 1
-
- if clip_frame_idx - 1 == 0:
- if DEBUG == "1":
- print(
- f"[TIMING],start_get_clips,{clip_key},{time.time()}",
- flush=True,
- )
- _out_vid = cv2.VideoWriter(
- tmp_file,
- fourcc=webcam_stream.fourcc,
- fps=webcam_stream.target_fps,
- frameSize=(webcam_stream.width, webcam_stream.height),
- )
- if DEBUG == "1":
- print(
- f"[TIMING],Start new clip,{clip_key},{time.time()}",
- flush=True,
- )
-
- _out_vid.write(frame)
-
- if clip_frame_idx == webcam_stream.clip_total_frames:
- frame_count = clip_frame_idx
- _out_vid = save_clip(
- clip_filename,
- clip_id,
- tmp_file,
- _out_vid,
- frame_count,
- frameNum,
- webcam_stream.target_fps,
- )
- queue_details = (
- clip_key,
- clip_filename,
- webcam_stream.width,
- webcam_stream.height,
- )
- send_metadata_queue.put(queue_details)
-
- webcam_stream.stop()
-
- end = time.time()
-
- # printing time elapsed and fps
- if DEBUG == "1":
- elapsed = end - start
- print(
- "[DEBUG] Stream name:{}, FPS: {} , Elapsed Time: {}, Num. Retrieved Frames: {}, Num. Processed Frames: {}".format(
- webcam_stream.stream_name,
- webcam_stream.target_fps,
- elapsed,
- webcam_stream.retrieved_frames,
- webcam_stream.num_frames_processed,
- ),
- flush=True,
- )
-
- print(
- f"[TIMING],Completed processing,{webcam_stream.stream_name},{end}",
- flush=True,
- )
-
-
-""" MAIN FUNCTION """
-
-if __name__ == "__main__":
- camera_src = sys.argv[1]
- camera_name = sys.argv[2] if len(sys.argv) == 3 else None
-
- process_stream(str(camera_src), camera_name)
diff --git a/video/requirements.CPU.txt b/video/requirements.CPU.txt
deleted file mode 100644
index 4828f07..0000000
--- a/video/requirements.CPU.txt
+++ /dev/null
@@ -1,1161 +0,0 @@
-about-time==4.2.1 \
- --hash=sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece \
- --hash=sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341
-alive-progress==3.3.0 \
- --hash=sha256:457dd2428b48dacd49854022a46448d236a48f1b7277874071c39395307e830c \
- --hash=sha256:63dd33bb94cde15ad9e5b666dbba8fedf71b72a4935d6fb9a92931e69402c9ff
-attrs==25.4.0 \
- --hash=sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11 \
- --hash=sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373
-autograd==1.8.0 \
- --hash=sha256:107374ded5b09fc8643ac925348c0369e7b0e73bbed9565ffd61b8fd04425683 \
- --hash=sha256:4ab9084294f814cf56c280adbe19612546a35574d67c574b04933c7d2ecb7d78
-cffi==2.0.0 \
- --hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \
- --hash=sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b \
- --hash=sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f \
- --hash=sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9 \
- --hash=sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44 \
- --hash=sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2 \
- --hash=sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c \
- --hash=sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 \
- --hash=sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65 \
- --hash=sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e \
- --hash=sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a \
- --hash=sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e \
- --hash=sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25 \
- --hash=sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a \
- --hash=sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe \
- --hash=sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b \
- --hash=sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91 \
- --hash=sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592 \
- --hash=sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187 \
- --hash=sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c \
- --hash=sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1 \
- --hash=sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94 \
- --hash=sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba \
- --hash=sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb \
- --hash=sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165 \
- --hash=sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529 \
- --hash=sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca \
- --hash=sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c \
- --hash=sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6 \
- --hash=sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c \
- --hash=sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0 \
- --hash=sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743 \
- --hash=sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63 \
- --hash=sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5 \
- --hash=sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5 \
- --hash=sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4 \
- --hash=sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d \
- --hash=sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b \
- --hash=sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93 \
- --hash=sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205 \
- --hash=sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27 \
- --hash=sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512 \
- --hash=sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d \
- --hash=sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c \
- --hash=sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037 \
- --hash=sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 \
- --hash=sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322 \
- --hash=sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb \
- --hash=sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c \
- --hash=sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8 \
- --hash=sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4 \
- --hash=sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414 \
- --hash=sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9 \
- --hash=sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664 \
- --hash=sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9 \
- --hash=sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775 \
- --hash=sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739 \
- --hash=sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc \
- --hash=sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062 \
- --hash=sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe \
- --hash=sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9 \
- --hash=sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92 \
- --hash=sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5 \
- --hash=sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13 \
- --hash=sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d \
- --hash=sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 \
- --hash=sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f \
- --hash=sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495 \
- --hash=sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b \
- --hash=sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6 \
- --hash=sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c \
- --hash=sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef \
- --hash=sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5 \
- --hash=sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18 \
- --hash=sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad \
- --hash=sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3 \
- --hash=sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7 \
- --hash=sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5 \
- --hash=sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534 \
- --hash=sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49 \
- --hash=sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2 \
- --hash=sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5 \
- --hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \
- --hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf
-cma==4.4.1 \
- --hash=sha256:61177b54f12bfeeac307970f8caefd8210dfa1d43a2da34e9ef8b1416d930fd8 \
- --hash=sha256:bf0621d4f52cf3354be3d0a5cd439ffed52f24f429ab02c23cf0f8ca16d427d8
-contourpy==1.3.2 \
- --hash=sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f \
- --hash=sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92 \
- --hash=sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16 \
- --hash=sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f \
- --hash=sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f \
- --hash=sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7 \
- --hash=sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e \
- --hash=sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08 \
- --hash=sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841 \
- --hash=sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5 \
- --hash=sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2 \
- --hash=sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415 \
- --hash=sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878 \
- --hash=sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0 \
- --hash=sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab \
- --hash=sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445 \
- --hash=sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43 \
- --hash=sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c \
- --hash=sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823 \
- --hash=sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69 \
- --hash=sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15 \
- --hash=sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef \
- --hash=sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5 \
- --hash=sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73 \
- --hash=sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9 \
- --hash=sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912 \
- --hash=sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5 \
- --hash=sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85 \
- --hash=sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d \
- --hash=sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631 \
- --hash=sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2 \
- --hash=sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54 \
- --hash=sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773 \
- --hash=sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934 \
- --hash=sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a \
- --hash=sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441 \
- --hash=sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422 \
- --hash=sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532 \
- --hash=sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739 \
- --hash=sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b \
- --hash=sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f \
- --hash=sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1 \
- --hash=sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87 \
- --hash=sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52 \
- --hash=sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1 \
- --hash=sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd \
- --hash=sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989 \
- --hash=sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb \
- --hash=sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f \
- --hash=sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad \
- --hash=sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9 \
- --hash=sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512 \
- --hash=sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd \
- --hash=sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83 \
- --hash=sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe \
- --hash=sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0 \
- --hash=sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c
-cycler==0.12.1 \
- --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \
- --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c
-deprecated==1.3.1 \
- --hash=sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f \
- --hash=sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223
-filelock==3.25.2 \
- --hash=sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694 \
- --hash=sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70
-fonttools==4.61.1 \
- --hash=sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87 \
- --hash=sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796 \
- --hash=sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75 \
- --hash=sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d \
- --hash=sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371 \
- --hash=sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b \
- --hash=sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b \
- --hash=sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2 \
- --hash=sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3 \
- --hash=sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9 \
- --hash=sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd \
- --hash=sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c \
- --hash=sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c \
- --hash=sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56 \
- --hash=sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37 \
- --hash=sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0 \
- --hash=sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958 \
- --hash=sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5 \
- --hash=sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118 \
- --hash=sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69 \
- --hash=sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9 \
- --hash=sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261 \
- --hash=sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb \
- --hash=sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47 \
- --hash=sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24 \
- --hash=sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c \
- --hash=sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba \
- --hash=sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c \
- --hash=sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91 \
- --hash=sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1 \
- --hash=sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19 \
- --hash=sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6 \
- --hash=sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5 \
- --hash=sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2 \
- --hash=sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d \
- --hash=sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881 \
- --hash=sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063 \
- --hash=sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7 \
- --hash=sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09 \
- --hash=sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da \
- --hash=sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e \
- --hash=sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e \
- --hash=sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8 \
- --hash=sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa \
- --hash=sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6 \
- --hash=sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e \
- --hash=sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a \
- --hash=sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c \
- --hash=sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7 \
- --hash=sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd
-fsspec==2026.2.0 \
- --hash=sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff \
- --hash=sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437
-graphemeu==0.7.2 \
- --hash=sha256:1444520f6899fd30114fc2a39f297d86d10fa0f23bf7579f772f8bc7efaa2542 \
- --hash=sha256:42bbe373d7c146160f286cd5f76b1a8ad29172d7333ce10705c5cc282462a4f8
-jinja2==3.1.6 \
- --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
-joblib==1.5.3 \
- --hash=sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713 \
- --hash=sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3
-jsonschema==4.26.0 \
- --hash=sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326 \
- --hash=sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce
-jsonschema-specifications==2025.9.1 \
- --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe \
- --hash=sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d
-kiwisolver==1.4.9 \
- --hash=sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c \
- --hash=sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7 \
- --hash=sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21 \
- --hash=sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e \
- --hash=sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff \
- --hash=sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7 \
- --hash=sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c \
- --hash=sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26 \
- --hash=sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa \
- --hash=sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f \
- --hash=sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1 \
- --hash=sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891 \
- --hash=sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77 \
- --hash=sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543 \
- --hash=sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d \
- --hash=sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce \
- --hash=sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3 \
- --hash=sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60 \
- --hash=sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a \
- --hash=sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089 \
- --hash=sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab \
- --hash=sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78 \
- --hash=sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771 \
- --hash=sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f \
- --hash=sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b \
- --hash=sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14 \
- --hash=sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32 \
- --hash=sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527 \
- --hash=sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185 \
- --hash=sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634 \
- --hash=sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed \
- --hash=sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1 \
- --hash=sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c \
- --hash=sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11 \
- --hash=sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752 \
- --hash=sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5 \
- --hash=sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4 \
- --hash=sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58 \
- --hash=sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5 \
- --hash=sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198 \
- --hash=sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536 \
- --hash=sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134 \
- --hash=sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf \
- --hash=sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2 \
- --hash=sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2 \
- --hash=sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370 \
- --hash=sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1 \
- --hash=sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154 \
- --hash=sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b \
- --hash=sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197 \
- --hash=sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386 \
- --hash=sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a \
- --hash=sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48 \
- --hash=sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748 \
- --hash=sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c \
- --hash=sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8 \
- --hash=sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5 \
- --hash=sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999 \
- --hash=sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369 \
- --hash=sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122 \
- --hash=sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b \
- --hash=sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 \
- --hash=sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9 \
- --hash=sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f \
- --hash=sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799 \
- --hash=sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028 \
- --hash=sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2 \
- --hash=sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525 \
- --hash=sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d \
- --hash=sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb \
- --hash=sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872 \
- --hash=sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64 \
- --hash=sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586 \
- --hash=sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf \
- --hash=sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552 \
- --hash=sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2 \
- --hash=sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415 \
- --hash=sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c \
- --hash=sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6 \
- --hash=sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64 \
- --hash=sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d \
- --hash=sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548 \
- --hash=sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07 \
- --hash=sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61 \
- --hash=sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d \
- --hash=sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771 \
- --hash=sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9 \
- --hash=sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c \
- --hash=sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3 \
- --hash=sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16 \
- --hash=sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145 \
- --hash=sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611 \
- --hash=sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2 \
- --hash=sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464 \
- --hash=sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2 \
- --hash=sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04 \
- --hash=sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54 \
- --hash=sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df \
- --hash=sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f \
- --hash=sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1 \
- --hash=sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220
-markdown-it-py==4.0.0 \
- --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \
- --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3
-markupsafe==3.0.3 \
- --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591
-matplotlib==3.10.8 \
- --hash=sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7 \
- --hash=sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a \
- --hash=sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f \
- --hash=sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3 \
- --hash=sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5 \
- --hash=sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9 \
- --hash=sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2 \
- --hash=sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3 \
- --hash=sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6 \
- --hash=sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f \
- --hash=sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b \
- --hash=sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8 \
- --hash=sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008 \
- --hash=sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b \
- --hash=sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656 \
- --hash=sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958 \
- --hash=sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04 \
- --hash=sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b \
- --hash=sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6 \
- --hash=sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908 \
- --hash=sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c \
- --hash=sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1 \
- --hash=sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d \
- --hash=sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1 \
- --hash=sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c \
- --hash=sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a \
- --hash=sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce \
- --hash=sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a \
- --hash=sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160 \
- --hash=sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1 \
- --hash=sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11 \
- --hash=sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a \
- --hash=sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466 \
- --hash=sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486 \
- --hash=sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78 \
- --hash=sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17 \
- --hash=sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077 \
- --hash=sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565 \
- --hash=sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f \
- --hash=sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50 \
- --hash=sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58 \
- --hash=sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2 \
- --hash=sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645 \
- --hash=sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2 \
- --hash=sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39 \
- --hash=sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf \
- --hash=sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149 \
- --hash=sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22 \
- --hash=sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df \
- --hash=sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4 \
- --hash=sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933 \
- --hash=sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6 \
- --hash=sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8 \
- --hash=sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a \
- --hash=sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7
-mdurl==0.1.2 \
- --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
- --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
-moocore==0.2.0 \
- --hash=sha256:3dc601f85f9a4743ed50ddd027dca30e3bb55c899916a092c2ece495b1b2de08 \
- --hash=sha256:653449231f328d3c9e69693ec3d44e8c77f38ab7e9ef0c69dd9ded40449e980d \
- --hash=sha256:a7683feddfd2a47b4a0f89ee8d370cae72331792f68e67f083ccb37bb2f1c8cf \
- --hash=sha256:b90c7bde2164f9b95c6b2e870f0ca6ccc5dabff2bf8086162d7318c770e5868f \
- --hash=sha256:cf8f091a7304532ed605acd82acd051e89af22ece8e2a27a3cee0faf9f2ea185 \
- --hash=sha256:d0699b770b5eebdeac477a356d539efa8807c6cf067a453a0682e0df2299a512 \
- --hash=sha256:e93c07062adefd0fcba73a521f325f7fb874f2af92aaeec203cf9db31a41894b \
- --hash=sha256:ea057409731e73dbc4ba4214cbf7747309695b01314f8786678b758cc9c561c4
-mpmath==1.3.0 \
- --hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \
- --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
-natsort==8.4.0 \
- --hash=sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581 \
- --hash=sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c
-networkx==3.4.2 \
- --hash=sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1 \
- --hash=sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f
-ninja==1.13.0 \
- --hash=sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f \
- --hash=sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988 \
- --hash=sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9 \
- --hash=sha256:3d00c692fb717fd511abeb44b8c5d00340c36938c12d6538ba989fe764e79630 \
- --hash=sha256:3d7d7779d12cb20c6d054c61b702139fd23a7a964ec8f2c823f1ab1b084150db \
- --hash=sha256:4a40ce995ded54d9dc24f8ea37ff3bf62ad192b547f6c7126e7e25045e76f978 \
- --hash=sha256:4be9c1b082d244b1ad7ef41eb8ab088aae8c109a9f3f0b3e56a252d3e00f42c1 \
- --hash=sha256:5f8e1e8a1a30835eeb51db05cf5a67151ad37542f5a4af2a438e9490915e5b72 \
- --hash=sha256:60056592cf495e9a6a4bea3cd178903056ecb0943e4de45a2ea825edb6dc8d3e \
- --hash=sha256:6739d3352073341ad284246f81339a384eec091d9851a886dfa5b00a6d48b3e2 \
- --hash=sha256:8cfbb80b4a53456ae8a39f90ae3d7a2129f45ea164f43fadfa15dc38c4aef1c9 \
- --hash=sha256:aa45b4037b313c2f698bc13306239b8b93b4680eb47e287773156ac9e9304714 \
- --hash=sha256:b4f2a072db3c0f944c32793e91532d8948d20d9ab83da9c0c7c15b5768072200 \
- --hash=sha256:be7f478ff9f96a128b599a964fc60a6a87b9fa332ee1bd44fa243ac88d50291c \
- --hash=sha256:d741a5e6754e0bda767e3274a0f0deeef4807f1fec6c0d7921a0244018926ae5 \
- --hash=sha256:e8bad11f8a00b64137e9b315b137d8bb6cbf3086fbdc43bf1f90fd33324d2e96 \
- --hash=sha256:fa2a8bfc62e31b08f83127d1613d10821775a0eb334197154c4d6067b7068ff1 \
- --hash=sha256:fb46acf6b93b8dd0322adc3a4945452a4e774b75b91293bafcc7b7f8e6517dfa \
- --hash=sha256:fb8ee8719f8af47fed145cced4a85f0755dd55d45b2bddaf7431fa89803c5f3e
-nncf==2.19.0 \
- --hash=sha256:6c782b0e5a8120d36a3249cd61edaf311b6270dd90095661700658899726e523 \
- --hash=sha256:d32a9667e6b2837557bdd340895bb49e8d103eb1ec004031386e8c2bb4554cac
-numpy==2.2.6 \
- --hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \
- --hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \
- --hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \
- --hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
- --hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \
- --hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \
- --hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \
- --hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \
- --hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
- --hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \
- --hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \
- --hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
- --hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \
- --hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \
- --hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \
- --hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
- --hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \
- --hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \
- --hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \
- --hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \
- --hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
- --hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
- --hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \
- --hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
- --hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \
- --hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
- --hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \
- --hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \
- --hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
- --hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
- --hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \
- --hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \
- --hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
- --hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \
- --hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
- --hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
- --hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
- --hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
- --hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
- --hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \
- --hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
- --hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \
- --hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
- --hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
- --hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
- --hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \
- --hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \
- --hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \
- --hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \
- --hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
- --hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \
- --hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \
- --hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \
- --hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \
- --hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8
-openvino-telemetry==2025.2.0 \
- --hash=sha256:8bf8127218e51e99547bf38b8fb85a8b31c9bf96e6f3a82eb0b3b6a34155977c \
- --hash=sha256:bcb667e83a44f202ecf4cfa49281715c6d7e21499daec04ff853b7f964833599
-packaging==24.1 \
- --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
-pandas==2.3.3 \
- --hash=sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7 \
- --hash=sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593 \
- --hash=sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5 \
- --hash=sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791 \
- --hash=sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73 \
- --hash=sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec \
- --hash=sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4 \
- --hash=sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5 \
- --hash=sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac \
- --hash=sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084 \
- --hash=sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c \
- --hash=sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87 \
- --hash=sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35 \
- --hash=sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250 \
- --hash=sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c \
- --hash=sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826 \
- --hash=sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9 \
- --hash=sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713 \
- --hash=sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1 \
- --hash=sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523 \
- --hash=sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3 \
- --hash=sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78 \
- --hash=sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53 \
- --hash=sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c \
- --hash=sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21 \
- --hash=sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5 \
- --hash=sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff \
- --hash=sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45 \
- --hash=sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110 \
- --hash=sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493 \
- --hash=sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b \
- --hash=sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450 \
- --hash=sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86 \
- --hash=sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8 \
- --hash=sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98 \
- --hash=sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89 \
- --hash=sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66 \
- --hash=sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b \
- --hash=sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8 \
- --hash=sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29 \
- --hash=sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6 \
- --hash=sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc \
- --hash=sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2 \
- --hash=sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788 \
- --hash=sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa \
- --hash=sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151 \
- --hash=sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838 \
- --hash=sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b \
- --hash=sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a \
- --hash=sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d \
- --hash=sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908 \
- --hash=sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0 \
- --hash=sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b \
- --hash=sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c \
- --hash=sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee
-pillow==12.1.1 \
- --hash=sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9 \
- --hash=sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da \
- --hash=sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f \
- --hash=sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642 \
- --hash=sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713 \
- --hash=sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850 \
- --hash=sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9 \
- --hash=sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0 \
- --hash=sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9 \
- --hash=sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8 \
- --hash=sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6 \
- --hash=sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd \
- --hash=sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5 \
- --hash=sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c \
- --hash=sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35 \
- --hash=sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1 \
- --hash=sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff \
- --hash=sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38 \
- --hash=sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4 \
- --hash=sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af \
- --hash=sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60 \
- --hash=sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986 \
- --hash=sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13 \
- --hash=sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717 \
- --hash=sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e \
- --hash=sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b \
- --hash=sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15 \
- --hash=sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a \
- --hash=sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb \
- --hash=sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d \
- --hash=sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b \
- --hash=sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e \
- --hash=sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a \
- --hash=sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f \
- --hash=sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a \
- --hash=sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce \
- --hash=sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc \
- --hash=sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f \
- --hash=sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586 \
- --hash=sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f \
- --hash=sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9 \
- --hash=sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8 \
- --hash=sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40 \
- --hash=sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60 \
- --hash=sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c \
- --hash=sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0 \
- --hash=sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334 \
- --hash=sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af \
- --hash=sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735 \
- --hash=sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524 \
- --hash=sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf \
- --hash=sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b \
- --hash=sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2 \
- --hash=sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9 \
- --hash=sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7 \
- --hash=sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e \
- --hash=sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4 \
- --hash=sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4 \
- --hash=sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b \
- --hash=sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397 \
- --hash=sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c \
- --hash=sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e \
- --hash=sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029 \
- --hash=sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3 \
- --hash=sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052 \
- --hash=sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984 \
- --hash=sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293 \
- --hash=sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523 \
- --hash=sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f \
- --hash=sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b \
- --hash=sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80 \
- --hash=sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f \
- --hash=sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79 \
- --hash=sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23 \
- --hash=sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8 \
- --hash=sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e \
- --hash=sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3 \
- --hash=sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e \
- --hash=sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36 \
- --hash=sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f \
- --hash=sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5 \
- --hash=sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f \
- --hash=sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6 \
- --hash=sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32 \
- --hash=sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20 \
- --hash=sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202 \
- --hash=sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0 \
- --hash=sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3 \
- --hash=sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563 \
- --hash=sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090 \
- --hash=sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289
-pip==26.0.1 \
- --hash=sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b \
- --hash=sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8
-platformdirs==4.5.1 \
- --hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \
- --hash=sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31
-protobuf==5.29.6 \
- --hash=sha256:36ade6ff88212e91aef4e687a971a11d7d24d6948a66751abc1b3238648f5d05 \
- --hash=sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1 \
- --hash=sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86 \
- --hash=sha256:76e07e6567f8baf827137e8d5b8204b6c7b6488bbbff1bf0a72b383f77999c18 \
- --hash=sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda \
- --hash=sha256:831e2da16b6cc9d8f1654c041dd594eda43391affd3c03a91bea7f7f6da106d6 \
- --hash=sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6 \
- --hash=sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269 \
- --hash=sha256:cb4c86de9cd8a7f3a256b9744220d87b847371c6b2f10bde87768918ef33ba49 \
- --hash=sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723 \
- --hash=sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9
-psutil==7.2.1 \
- --hash=sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1 \
- --hash=sha256:08a2f175e48a898c8eb8eace45ce01777f4785bc744c90aa2cc7f2fa5462a266 \
- --hash=sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442 \
- --hash=sha256:2ceae842a78d1603753561132d5ad1b2f8a7979cb0c283f5b52fb4e6e14b1a79 \
- --hash=sha256:35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf \
- --hash=sha256:3fce5f92c22b00cdefd1645aa58ab4877a01679e901555067b1bd77039aa589f \
- --hash=sha256:494c513ccc53225ae23eec7fe6e1482f1b8a44674241b54561f755a898650679 \
- --hash=sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8 \
- --hash=sha256:81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49 \
- --hash=sha256:923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f \
- --hash=sha256:93f3f7b0bb07711b49626e7940d6fe52aa9940ad86e8f7e74842e73189712129 \
- --hash=sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67 \
- --hash=sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6 \
- --hash=sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17 \
- --hash=sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42 \
- --hash=sha256:ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d \
- --hash=sha256:cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672 \
- --hash=sha256:d34d2ca888208eea2b5c68186841336a7f5e0b990edec929be909353a202768a \
- --hash=sha256:ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc \
- --hash=sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3 \
- --hash=sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8
-pycparser==3.0 \
- --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
- --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
-pydot==3.0.4 \
- --hash=sha256:3ce88b2558f3808b0376f22bfa6c263909e1c3981e2a7b629b65b451eee4a25d \
- --hash=sha256:bfa9c3fc0c44ba1d132adce131802d7df00429d1a79cc0346b0a5cd374dbe9c6
-pygments==2.20.0 \
- --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
- --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
-pymoo==0.6.1.6 \
- --hash=sha256:007bdabe638d5cc1ff98d1df096e7cea42f266ecc7b84fc0aac695a20b6f02f6 \
- --hash=sha256:01e848762e04d183e8dcc12e1cfb1e2cdd2c5efeda47961dbcf6c86c09751e30 \
- --hash=sha256:2a2e9524b07baef135a7143700cc65a504c2123c717a6924fa939947b4fdba73 \
- --hash=sha256:38fa5bf507cd7c07eae0286948db79f761b5b4c5d5956a31fd0e5a771c83fa47 \
- --hash=sha256:3b9dcb6959cf2b12c0f1d4ac971fa53074430eb976367b2e7eec0d0301423f31 \
- --hash=sha256:466f88073c9f498bcf75334a2e47279968003d91b7fe01df1b056b5a6c3434d8 \
- --hash=sha256:49a9cde6ff7cc8497458166e2c14734bf4879c76256412cce0de2af86d2b71a8 \
- --hash=sha256:50472dfb93b90b4801c63070a4afc786c52f7f56b345f345024b39cd35ff06c4 \
- --hash=sha256:5086f46020aa1a13a0e3142ef87b6487e91ea5d25b7b69467039f0d3f5a57cb2 \
- --hash=sha256:533de86c284bf3b7b6da871e2ae07a914eaa5d3a51f5c4e6d7c055d3471c4467 \
- --hash=sha256:5607e0359c96691158192bd3e2b939beab6d4ed25032ef4cb79edf588c2bb475 \
- --hash=sha256:56ecb6f9f5ac0559829e183a23ea3672eae8c1eb9d745305bef98da8d1614d28 \
- --hash=sha256:62599637724f74820e16d813e6ca86af1733add4251e22b5a6c677df75dec06b \
- --hash=sha256:642308eae98c5b8aee96272e10c6409dd97289ef214ec7383b39425f01549b5f \
- --hash=sha256:718b9e971e436c2ded520c4a6fe8c83b7f305da1fcc539d6386d025c6e9bfe66 \
- --hash=sha256:791188677b42e104c41cd1c65370a94fbbec756d8894947863f42a1f76352c90 \
- --hash=sha256:798e5af41527324303e56ac806f9ec34c65f31c2df9e904daeca68027bf91266 \
- --hash=sha256:7c042120df86240c1451e2a1afb0f76b64acb8d736fb7687bc5e4aec40f7ff9e \
- --hash=sha256:7d13e14434f0555fbeda20764d34be4dc94f53ae7040a337928d7bfe42d3422a \
- --hash=sha256:819a27ff19c146a64853a86ebab02bc6bbf28fb9fcf119e93cfc855ec6314f72 \
- --hash=sha256:981bd5b8581e959814a0e51982be1a964945fb7f599cfe7857a63cbc1ad97d58 \
- --hash=sha256:a4f590fc62021d2583afdc88a2eb5ec878699786585f9ca82edf1fc32557ac96 \
- --hash=sha256:a68fb9d807c1cb6f36e13c24731c496b1e98b9c8810125072f19bd56baa6a4d5 \
- --hash=sha256:b3e990dea0ad84fa2f89462215e34960579dbb9fef4d880aecb09f50cc18e713 \
- --hash=sha256:b9dd7da8b18bffac6b09c75b4a4a645232994d91fc0cdfe39baffdc08fa5d252 \
- --hash=sha256:c16e05b36e081d0ea6aee499346f65466811f63f5837f0d09de866624cb7355b \
- --hash=sha256:c8cfd085b74b3f673378109d8d0734d0cab460e3e78c09f691120ac0c2698aad \
- --hash=sha256:cd2b0fb19ce3e71a177ed3b965ce2d4334ffb6aad8f621c124de6dafa09b7758 \
- --hash=sha256:d48077c7b612b149e7db5351459bf96a0950e84ebcd5b7b953bf46b3dcf1ac28 \
- --hash=sha256:df9520fd7fab0761d5698dd36eabcfb18b5eb970ddf01e10eb30b764e4bb72af \
- --hash=sha256:e0a42b6f3c26b2725b1edb1d4cfb98e5640bb0638813080ba1aa0daf23b2fd05 \
- --hash=sha256:ecf1ce515e72d59b78c909aa142d2369cc3c6d771ac7c9162831c0b62a869c8f \
- --hash=sha256:f2d48fa7f8f8ea314405ad49a6b7f3dcf8f12c2024e319e2d47ede0937f8b39b \
- --hash=sha256:f41b359d6de6c9d99ab7da94f115c5aea4d21e74a252bef7c8debec8f20f0b9f \
- --hash=sha256:f62fb1e2ba15f22ff5569c6d364dbf9dfe8b3722a114e6bc1df3cc10f8dd5044 \
- --hash=sha256:fd4715b4015b10ad3cf02e5eb9abb57fe4bfa4d3378f9076b76d60c1ddfa46d6
-pyparsing==3.3.2 \
- --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
- --hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
-python-dateutil==2.9.0.post0 \
- --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
- --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
-pytz==2025.2 \
- --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \
- --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00
-referencing==0.37.0 \
- --hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \
- --hash=sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8
-rich==14.2.0 \
- --hash=sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4 \
- --hash=sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd
-rpds-py==0.30.0 \
- --hash=sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f \
- --hash=sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136 \
- --hash=sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3 \
- --hash=sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7 \
- --hash=sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65 \
- --hash=sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4 \
- --hash=sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169 \
- --hash=sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf \
- --hash=sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4 \
- --hash=sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2 \
- --hash=sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c \
- --hash=sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4 \
- --hash=sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3 \
- --hash=sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6 \
- --hash=sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7 \
- --hash=sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89 \
- --hash=sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85 \
- --hash=sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6 \
- --hash=sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa \
- --hash=sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb \
- --hash=sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6 \
- --hash=sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87 \
- --hash=sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856 \
- --hash=sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4 \
- --hash=sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f \
- --hash=sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53 \
- --hash=sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229 \
- --hash=sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad \
- --hash=sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23 \
- --hash=sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db \
- --hash=sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038 \
- --hash=sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27 \
- --hash=sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00 \
- --hash=sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18 \
- --hash=sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083 \
- --hash=sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c \
- --hash=sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738 \
- --hash=sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898 \
- --hash=sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e \
- --hash=sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7 \
- --hash=sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08 \
- --hash=sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6 \
- --hash=sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551 \
- --hash=sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e \
- --hash=sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288 \
- --hash=sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df \
- --hash=sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0 \
- --hash=sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2 \
- --hash=sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05 \
- --hash=sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0 \
- --hash=sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464 \
- --hash=sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5 \
- --hash=sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404 \
- --hash=sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7 \
- --hash=sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139 \
- --hash=sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394 \
- --hash=sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb \
- --hash=sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15 \
- --hash=sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff \
- --hash=sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed \
- --hash=sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6 \
- --hash=sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e \
- --hash=sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95 \
- --hash=sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d \
- --hash=sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950 \
- --hash=sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3 \
- --hash=sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5 \
- --hash=sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97 \
- --hash=sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e \
- --hash=sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e \
- --hash=sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b \
- --hash=sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd \
- --hash=sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad \
- --hash=sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8 \
- --hash=sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425 \
- --hash=sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221 \
- --hash=sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d \
- --hash=sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825 \
- --hash=sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51 \
- --hash=sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e \
- --hash=sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f \
- --hash=sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8 \
- --hash=sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f \
- --hash=sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d \
- --hash=sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07 \
- --hash=sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877 \
- --hash=sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31 \
- --hash=sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58 \
- --hash=sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94 \
- --hash=sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28 \
- --hash=sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000 \
- --hash=sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1 \
- --hash=sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1 \
- --hash=sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7 \
- --hash=sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7 \
- --hash=sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40 \
- --hash=sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d \
- --hash=sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0 \
- --hash=sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84 \
- --hash=sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f \
- --hash=sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a \
- --hash=sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7 \
- --hash=sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419 \
- --hash=sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8 \
- --hash=sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a \
- --hash=sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9 \
- --hash=sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be \
- --hash=sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed \
- --hash=sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a \
- --hash=sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d \
- --hash=sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324 \
- --hash=sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f \
- --hash=sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2 \
- --hash=sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f \
- --hash=sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5
-safetensors==0.7.0 \
- --hash=sha256:0071bffba4150c2f46cae1432d31995d77acfd9f8db598b5d1a2ce67e8440ad2 \
- --hash=sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0 \
- --hash=sha256:12f49080303fa6bb424b362149a12949dfbbf1e06811a88f2307276b0c131afd \
- --hash=sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981 \
- --hash=sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a \
- --hash=sha256:473b32699f4200e69801bf5abf93f1a4ecd432a70984df164fc22ccf39c4a6f3 \
- --hash=sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d \
- --hash=sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0 \
- --hash=sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85 \
- --hash=sha256:6999421eb8ba9df4450a16d9184fcb7bef26240b9f98e95401f17af6c2210b71 \
- --hash=sha256:7b95a3fa7b3abb9b5b0e07668e808364d0d40f6bbbf9ae0faa8b5b210c97b140 \
- --hash=sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104 \
- --hash=sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57 \
- --hash=sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4 \
- --hash=sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba \
- --hash=sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517 \
- --hash=sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b \
- --hash=sha256:cfdead2f57330d76aa7234051dadfa7d4eedc0e5a27fd08e6f96714a92b00f09 \
- --hash=sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755 \
- --hash=sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48 \
- --hash=sha256:dc92bc2db7b45bda4510e4f51c59b00fe80b2d6be88928346e4294ce1c2abe7c \
- --hash=sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542 \
- --hash=sha256:f4729811a6640d019a4b7ba8638ee2fd21fa5ca8c7e7bdf0fed62068fcaac737
-scikit-learn==1.7.2 \
- --hash=sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1 \
- --hash=sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7 \
- --hash=sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c \
- --hash=sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda \
- --hash=sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a \
- --hash=sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c \
- --hash=sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18 \
- --hash=sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f \
- --hash=sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973 \
- --hash=sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290 \
- --hash=sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c \
- --hash=sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f \
- --hash=sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0 \
- --hash=sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8 \
- --hash=sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d \
- --hash=sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96 \
- --hash=sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1 \
- --hash=sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106 \
- --hash=sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61 \
- --hash=sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c \
- --hash=sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8 \
- --hash=sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1 \
- --hash=sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe \
- --hash=sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476 \
- --hash=sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44 \
- --hash=sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8 \
- --hash=sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e \
- --hash=sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5 \
- --hash=sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b \
- --hash=sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615 \
- --hash=sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33
-scipy==1.15.3 \
- --hash=sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477 \
- --hash=sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c \
- --hash=sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723 \
- --hash=sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730 \
- --hash=sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539 \
- --hash=sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb \
- --hash=sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6 \
- --hash=sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594 \
- --hash=sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92 \
- --hash=sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82 \
- --hash=sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49 \
- --hash=sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759 \
- --hash=sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba \
- --hash=sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982 \
- --hash=sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8 \
- --hash=sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65 \
- --hash=sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4 \
- --hash=sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e \
- --hash=sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed \
- --hash=sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c \
- --hash=sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5 \
- --hash=sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5 \
- --hash=sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019 \
- --hash=sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e \
- --hash=sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1 \
- --hash=sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889 \
- --hash=sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca \
- --hash=sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825 \
- --hash=sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9 \
- --hash=sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62 \
- --hash=sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb \
- --hash=sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b \
- --hash=sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13 \
- --hash=sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb \
- --hash=sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40 \
- --hash=sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c \
- --hash=sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253 \
- --hash=sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb \
- --hash=sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f \
- --hash=sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163 \
- --hash=sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45 \
- --hash=sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7 \
- --hash=sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11 \
- --hash=sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf \
- --hash=sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e \
- --hash=sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126
-six==1.17.0 \
- --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
- --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
-sympy==1.14.0 \
- --hash=sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517 \
- --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5
-tabulate==0.9.0 \
- --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
- --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
-threadpoolctl==3.6.0 \
- --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \
- --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e
-torch==2.10.0+cpu \
- --hash=sha256:0e286fcf6ce0cc7b204396c9b4ea0d375f1f0c3e752f68ce3d3aeb265511db8c \
- --hash=sha256:0fedcb1a77e8f2aaf7bfd21591bf6d1e0b207473268c9be16b17cb7783253969 \
- --hash=sha256:106dd1930cb30a4a337366ba3f9b25318ebf940f51fd46f789281dd9e736bdc4 \
- --hash=sha256:170a0623108055be5199370335cf9b41ba6875b3cb6f086db4aee583331a4899 \
- --hash=sha256:179451716487f8cb09b56459667fa1f5c4c0946c1e75fbeae77cfc40a5768d87 \
- --hash=sha256:17a09465bab2aab8f0f273410297133d8d8fb6dd84dccbd252ca4a4f3a111847 \
- --hash=sha256:1cfcb9b1558c6e52dffd0d4effce83b13c5ae5d97338164c372048c21f9cfccb \
- --hash=sha256:21cb5436978ef47c823b7a813ff0f8c2892e266cfe0f1d944879b5fba81bf4e1 \
- --hash=sha256:23882f8d882460aca809882fc42f5e343bf07585274f929ced00177d1be1eb67 \
- --hash=sha256:2adc71fe471e98a608723bfc837f7e1929885ebb912c693597711e139c1cda41 \
- --hash=sha256:31ae44836c8b9bbd1a3943d29c7c7457709ddf7c6173aa34aefe9d2203e4c405 \
- --hash=sha256:3eaa727e6a73affa61564d86b9d03191df45c8650d0666bd3d57c8597ef61e78 \
- --hash=sha256:4fcd8b4cc2ae20f2b7749fb275349c55432393868778c2d50a08e81d5ee5591e \
- --hash=sha256:5af75e5f49de21b0bdf7672bc27139bd285f9e8dbcabe2d617a2eb656514ac36 \
- --hash=sha256:6c6f0df770144907092a0d067048d96ed4f278a6c840376d2ff0e27e7579b925 \
- --hash=sha256:8d316e5bf121f1eab1147e49ad0511a9d92e4c45cc357d1ab0bee440da71a095 \
- --hash=sha256:8de5a36371b775e2d4881ed12cc7f2de400b1ad3d728aa74a281f649f87c9b8c \
- --hash=sha256:9412bd37b70f5ebd1205242c4ba4cabae35a605947f2b30806d5c9b467936db9 \
- --hash=sha256:9accc30b56cb6756d4a9d04fcb8ebc0bb68c7d55c1ed31a8657397d316d31596 \
- --hash=sha256:a280ffaea7b9c828e0c1b9b3bd502d9b6a649dc9416997b69b84544bd469f215 \
- --hash=sha256:a28fdbcfa2fbacffec81300f24dd1bed2b0ccfdbed107a823cff12bc1db070f6 \
- --hash=sha256:aada8afc068add586464b2a55adb7cc9091eec55caf5320447204741cb6a0604 \
- --hash=sha256:b67d91326e4ed9eccbd6b7d84ed7ffa43f93103aa3f0b24145f3001f3b11b714 \
- --hash=sha256:b719da5af01b59126ac13eefd6ba3dd12d002dc0e8e79b8b365e55267a8189d3 \
- --hash=sha256:b7cb1ec66cefb90fd7b676eac72cfda3b8d4e4d0cacd7a531963bc2e0a9710ab \
- --hash=sha256:ba51ef01a510baf8fff576174f702c47e1aa54389a9f1fba323bb1a5003ff0bf \
- --hash=sha256:beadc2a6a1785b09a46daad378de91ef274b8d3eea7af0bc2d017d97f115afdf \
- --hash=sha256:c35c0de592941d4944698dbfa87271ab85d3370eca3b694943a2ab307ac34b3f \
- --hash=sha256:ce5c113d1f55f8c1f5af05047a24e50d11d293e0cbbb5bf7a75c6c761edd6eaa \
- --hash=sha256:d63ee6a80982fd73fe44bb70d97d2976e010312ff6db81d7bfb9167b06dd45b9 \
- --hash=sha256:e51994492cdb76edce29da88de3672a3022f9ef0ffd90345436948d4992be2c7 \
- --hash=sha256:e71c476517c33e7db69825a9ff46c7f47a723ec4dac5b2481cff4246d1c632be \
- --hash=sha256:ea2bcc9d1fca66974a71d4bf9a502539283f35d61fcab5a799b4e120846f1e02 \
- --hash=sha256:eb1bde1ce198f05c8770017de27e001d404499cf552aaaa014569eff56ca25c0 \
- --hash=sha256:ee40b8a4b4b2cf0670c6fd4f35a7ef23871af956fecb238fbf5da15a72650b1d \
- --hash=sha256:f8294fd2fc6dd8f4435a891a0122307a043b14b21f0dac1bca63c85bfb59e586 \
- --hash=sha256:fd215f3d0f681905c5b56b0630a3d666900a37fcc3ca5b937f95275c66f9fd9c \
- --hash=sha256:ffc8da9a1341092d6a90cb5b1c1a33cd61abf0fb43f0cd88443c27fa372c26ae
-torchvision==0.25.0+cpu \
- --hash=sha256:0c2d0da9bc011a0fde1d125af396a8fbe94d99becf9d313764f24ca7657a3448 \
- --hash=sha256:2d444009c0956669ada149f61ed78f257c1cc96d259efa6acf3929ca96ceb3f0 \
- --hash=sha256:3e2ae9981e32a5b9db685659d5c7af0f04b159ff20394650a90124baf6ada51a \
- --hash=sha256:499eae1e535766391b6ee2d1e6e841239c20e2e6d88203a15b8f9f8d60a1f8bd \
- --hash=sha256:4d72a57a8f0b5146e26dac1fbfa2c905280cd04f5fcb23b9c56253506b683aeb \
- --hash=sha256:59be99d1c470ef470b134468aa6afa6f968081a503acb4ee883d70332f822e35 \
- --hash=sha256:727334e9a721cfc1ac296ce0bf9e69d9486821bfa5b1e75a8feb6f78041db481 \
- --hash=sha256:73ce04dea64914ff1110008311204c366107d651d3b04faa0dbee77efb7133b7 \
- --hash=sha256:783c8fc580bbfc159bff52f4f72cdd538e42b32956e70dffa42b940db114e151 \
- --hash=sha256:7d47d544899fabac52ebe0d4812975608fd7ab79a3d7fb6383275eb667e33f53 \
- --hash=sha256:7f851245a2687743742157988ed9c42c6e312b95bbe6cfcac9e7d0d0c28ae62f \
- --hash=sha256:813f0106eb3e268f3783da67b882458e544c6fb72f946e6ca64b5ed4e62c6a77 \
- --hash=sha256:90eec299e1f82cfaf080ccb789df3838cb9a54b57e2ebe33852cd392c692de5c \
- --hash=sha256:9212210f417888e6261c040495180f053084812cf873dedba9fc51ff4b24b2d3 \
- --hash=sha256:9511339b3b5eb75229e0b5041202e8aed9bef3b1de3a715b9fb319c9e97688fd \
- --hash=sha256:aa016ab73e06a886f72edc8929ed2ed4c85aaaa6e10500ecdef921b03129b19e \
- --hash=sha256:c1be164e93c68b2dbf460fd58975377c892dbcf3358fb72941709c3857351bba \
- --hash=sha256:c7eb5f219fdfaf1f65e68c00eb81172ab4fa08a9874dae9dad2bca360da34d0f \
- --hash=sha256:e985e12a9a232618e5a43476de5689e4b14989f5da6b93909c57afa57ec27012 \
- --hash=sha256:fb9f07f6a10f0ac24ac482ae68c6df99110b74a0d80a4c64fddc9753267d8815 \
- --hash=sha256:fe54cbd5942cd0b26a90f1748f0d4421caf67be35c281c6c3b8573733a03d630
-typing-extensions==4.15.0 \
- --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
- --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
-tzdata==2025.3 \
- --hash=sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1 \
- --hash=sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7
-vdms==0.0.23 \
- --hash=sha256:2dec153f8cb33f27cdb9ab33125198195fd29a6777cf890fc735774ed9327874 \
- --hash=sha256:7cd93242df644947c009559f31b4e19eb1dab6b62ab783ce5a2a54a4ad392f57
-wrapt==2.0.1 \
- --hash=sha256:09c7476ab884b74dce081ad9bfd07fe5822d8600abade571cb1f66d5fc915af6 \
- --hash=sha256:0e17283f533a0d24d6e5429a7d11f250a58d28b4ae5186f8f47853e3e70d2590 \
- --hash=sha256:115cae4beed3542e37866469a8a1f2b9ec549b4463572b000611e9946b86e6f6 \
- --hash=sha256:1218573502a8235bb8a7ecaed12736213b22dcde9feab115fa2989d42b5ded45 \
- --hash=sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba \
- --hash=sha256:1e9b121e9aeb15df416c2c960b8255a49d44b4038016ee17af03975992d03931 \
- --hash=sha256:1f186e26ea0a55f809f232e92cc8556a0977e00183c3ebda039a807a42be1494 \
- --hash=sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c \
- --hash=sha256:23097ed8bc4c93b7bf36fa2113c6c733c976316ce0ee2c816f64ca06102034ef \
- --hash=sha256:2879af909312d0baf35f08edeea918ee3af7ab57c37fe47cb6a373c9f2749c7b \
- --hash=sha256:2afa23318136709c4b23d87d543b425c399887b4057936cd20386d5b1422b6fa \
- --hash=sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75 \
- --hash=sha256:35cdbd478607036fee40273be8ed54a451f5f23121bd9d4be515158f9498f7ad \
- --hash=sha256:36982b26f190f4d737f04a492a68accbfc6fa042c3f42326fdfbb6c5b7a20a31 \
- --hash=sha256:3793ac154afb0e5b45d1233cb94d354ef7a983708cc3bb12563853b1d8d53747 \
- --hash=sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62 \
- --hash=sha256:3cd1a4bd9a7a619922a8557e1318232e7269b5fb69d4ba97b04d20450a6bf970 \
- --hash=sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841 \
- --hash=sha256:3d366aa598d69416b5afedf1faa539fac40c1d80a42f6b236c88c73a3c8f2d41 \
- --hash=sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9 \
- --hash=sha256:3f373a4ab5dbc528a94334f9fe444395b23c2f5332adab9ff4ea82f5a9e33bc1 \
- --hash=sha256:3fa272ca34332581e00bf7773e993d4f632594eb2d1b0b162a9038df0fd971dd \
- --hash=sha256:47434236c396d04875180171ee1f3815ca1eada05e24a1ee99546320d54d1d1b \
- --hash=sha256:47b0f8bafe90f7736151f61482c583c86b0693d80f075a58701dd1549b0010a9 \
- --hash=sha256:4811e15d88ee62dbf5c77f2c3ff3932b1e3ac92323ba3912f51fc4016ce81ecf \
- --hash=sha256:49989061a9977a8cbd6d20f2efa813f24bf657c6990a42967019ce779a878dbf \
- --hash=sha256:4ae879acc449caa9ed43fc36ba08392b9412ee67941748d31d94e3cedb36628c \
- --hash=sha256:4b55cacc57e1dc2d0991dbe74c6419ffd415fb66474a02335cb10efd1aa3f84f \
- --hash=sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca \
- --hash=sha256:4da7384b0e5d4cae05c97cd6f94faaf78cc8b0f791fc63af43436d98c4ab37bb \
- --hash=sha256:4e54bbf554ee29fcceee24fa41c4d091398b911da6e7f5d7bffda963c9aed2e1 \
- --hash=sha256:50844efc8cdf63b2d90cd3d62d4947a28311e6266ce5235a219d21b195b4ec2c \
- --hash=sha256:5a4939eae35db6b6cec8e7aa0e833dcca0acad8231672c26c2a9ab7a0f8ac9c8 \
- --hash=sha256:5dc1b852337c6792aa111ca8becff5bacf576bf4a0255b0f05eb749da6a1643e \
- --hash=sha256:5e53b428f65ece6d9dad23cb87e64506392b720a0b45076c05354d27a13351a1 \
- --hash=sha256:61c4956171c7434634401db448371277d07032a81cc21c599c22953374781395 \
- --hash=sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9 \
- --hash=sha256:64b103acdaa53b7caf409e8d45d39a8442fe6dcfec6ba3f3d141e0cc2b5b4dbd \
- --hash=sha256:68424221a2dc00d634b54f92441914929c5ffb1c30b3b837343978343a3512a3 \
- --hash=sha256:6bd1a18f5a797fe740cb3d7a0e853a8ce6461cc62023b630caec80171a6b8097 \
- --hash=sha256:6c72328f668cf4c503ffcf9434c2b71fdd624345ced7941bc6693e61bbe36bef \
- --hash=sha256:6d2d947d266d99a1477cd005b23cbd09465276e302515e122df56bb9511aca1b \
- --hash=sha256:7164a55f5e83a9a0b031d3ffab4d4e36bbec42e7025db560f225489fa929e509 \
- --hash=sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf \
- --hash=sha256:7d539241e87b650cbc4c3ac9f32c8d1ac8a54e510f6dca3f6ab60dcfd48c9b10 \
- --hash=sha256:7de3cc939be0e1174969f943f3b44e0d79b6f9a82198133a5b7fc6cc92882f16 \
- --hash=sha256:8330b42d769965e96e01fa14034b28a2a7600fbf7e8f0cc90ebb36d492c993e4 \
- --hash=sha256:837e31620e06b16030b1d126ed78e9383815cbac914693f54926d816d35d8edf \
- --hash=sha256:83ce30937f0ba0d28818807b303a412440c4b63e39d3d8fc036a94764b728c92 \
- --hash=sha256:85df8d92158cb8f3965aecc27cf821461bb5f40b450b03facc5d9f0d4d6ddec6 \
- --hash=sha256:8639b843c9efd84675f1e100ed9e99538ebea7297b62c4b45a7042edb84db03e \
- --hash=sha256:89a82053b193837bf93c0f8a57ded6e4b6d88033a499dadff5067e912c2a41e9 \
- --hash=sha256:8bacfe6e001749a3b64db47bcf0341da757c95959f592823a93931a422395013 \
- --hash=sha256:8ec3303e8a81932171f455f792f8df500fc1a09f20069e5c16bd7049ab4e8e38 \
- --hash=sha256:90897ea1cf0679763b62e79657958cd54eae5659f6360fc7d2ccc6f906342183 \
- --hash=sha256:908f8c6c71557f4deaa280f55d0728c3bca0960e8c3dd5ceeeafb3c19942719d \
- --hash=sha256:91bcc576260a274b169c3098e9a3519fb01f2989f6d3d386ef9cbf8653de1374 \
- --hash=sha256:9219a1d946a9b32bb23ccae66bdb61e35c62773ce7ca6509ceea70f344656b7b \
- --hash=sha256:949520bccc1fa227274da7d03bf238be15389cd94e32e4297b92337df9b7a349 \
- --hash=sha256:98d873ed6c8b4ee2418f7afce666751854d6d03e3c0ec2a399bb039cd2ae89db \
- --hash=sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f \
- --hash=sha256:9ca66b38dd642bf90c59b6738af8070747b610115a39af2498535f62b5cdc1c3 \
- --hash=sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb \
- --hash=sha256:a52f93d95c8d38fed0669da2ebdb0b0376e895d84596a976c15a9eb45e3eccb3 \
- --hash=sha256:a9a83618c4f0757557c077ef71d708ddd9847ed66b7cc63416632af70d3e2308 \
- --hash=sha256:ab594f346517010050126fcd822697b25a7031d815bb4fbc238ccbe568216489 \
- --hash=sha256:ad3ee9d0f254851c71780966eb417ef8e72117155cff04821ab9b60549694a55 \
- --hash=sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b \
- --hash=sha256:b4c2e3d777e38e913b8ce3a6257af72fb608f86a1df471cb1d4339755d0a807c \
- --hash=sha256:b667189cf8efe008f55bbda321890bef628a67ab4147ebf90d182f2dadc78790 \
- --hash=sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684 \
- --hash=sha256:be9e84e91d6497ba62594158d3d31ec0486c60055c49179edc51ee43d095f79c \
- --hash=sha256:bf4cb76f36be5de950ce13e22e7fdf462b35b04665a12b64f3ac5c1bbbcf3728 \
- --hash=sha256:bfb5539005259f8127ea9c885bdc231978c06b7a980e63a8a61c8c4c979719d0 \
- --hash=sha256:c046781d422f0830de6329fa4b16796096f28a92c8aef3850674442cdcb87b7f \
- --hash=sha256:c1be685ac7700c966b8610ccc63c3187a72e33cab53526a27b2a285a662cd4f7 \
- --hash=sha256:c1c91405fcf1d501fa5d55df21e58ea49e6b879ae829f1039faaf7e5e509b41e \
- --hash=sha256:c235095d6d090aa903f1db61f892fffb779c1eaeb2a50e566b52001f7a0f66ed \
- --hash=sha256:c4012a2bd37059d04f8209916aa771dfb564cccb86079072bdcd48a308b6a5c5 \
- --hash=sha256:c5ef2f2b8a53b7caee2f797ef166a390fef73979b15778a4a153e4b5fedce8fa \
- --hash=sha256:c654eafb01afac55246053d67a4b9a984a3567c3808bb7df2f8de1c1caba2e1c \
- --hash=sha256:c8d60527d1ecfc131426b10d93ab5d53e08a09c5fa0175f6b21b3252080c70a9 \
- --hash=sha256:c9e850f5b7fc67af856ff054c71690d54fa940c3ef74209ad9f935b4f66a0233 \
- --hash=sha256:cbeb0971e13b4bd81d34169ed57a6dda017328d1a22b62fda45e1d21dd06148f \
- --hash=sha256:d1a8a09a004ef100e614beec82862d11fc17d601092c3599afd22b1f36e4137e \
- --hash=sha256:d67956c676be5a24102c7407a71f4126d30de2a569a1c7871c9f3cabc94225d7 \
- --hash=sha256:d6cc985b9c8b235bd933990cdbf0f891f8e010b65a3911f7a55179cd7b0fc57b \
- --hash=sha256:d7b822c61ed04ee6ad64bc90d13368ad6eb094db54883b5dde2182f67a7f22c0 \
- --hash=sha256:df0b6d3b95932809c5b3fecc18fda0f1e07452d05e2662a0b35548985f256e28 \
- --hash=sha256:e042d653a4745be832d5aa190ff80ee4f02c34b21f4b785745eceacd0907b815 \
- --hash=sha256:e2f84e9af2060e3904a32cea9bb6db23ce3f91cfd90c6b426757cf7cc01c45c7 \
- --hash=sha256:e3612dc06b436968dfb9142c62e5dfa9eb5924f91120b3c8ff501ad878f90eb3 \
- --hash=sha256:e505629359cb5f751e16e30cf3f91a1d3ddb4552480c205947da415d597f7ac2 \
- --hash=sha256:e60690ba71a57424c8d9ff28f8d006b7ad7772c22a4af432188572cd7fa004a1 \
- --hash=sha256:e76e3f91f864e89db8b8d2a8311d57df93f01ad6bb1e9b9976d1f2e83e18315c \
- --hash=sha256:eb7cffe572ad0a141a7886a1d2efa5bef0bf7fe021deeea76b3ab334d2c38218 \
- --hash=sha256:ec65a78fbd9d6f083a15d7613b2800d5663dbb6bb96003899c834beaa68b242c \
- --hash=sha256:eda8e4ecd662d48c28bb86be9e837c13e45c58b8300e43ba3c9b4fa9900302f7 \
- --hash=sha256:f26f8e2ca19564e2e1fdbb6a0e47f36e0efbab1acc31e15471fad88f828c75f6 \
- --hash=sha256:f49027b0b9503bf6c8cdc297ca55006b80c2f5dd36cecc72c6835ab6e10e8a25 \
- --hash=sha256:f73f9f7a0ebd0db139253d27e5fc8d2866ceaeef19c30ab5d69dcbe35e1a6981 \
- --hash=sha256:fa4184e74197af3adad3c889a1af95b53bb0466bced92ea99a0c014e48323eec \
- --hash=sha256:fb1a5b72cbd751813adc02ef01ada0b0d05d3dcbc32976ce189a1279d80ad4a2 \
- --hash=sha256:fb3a86e703868561c5cad155a15c36c716e1ab513b7065bd2ac8ed353c503333 \
- --hash=sha256:fc007fdf480c77301ab1afdbb6ab22a5deee8885f3b1ed7afcb7e5e84a0e27be \
- --hash=sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b \
- --hash=sha256:fec0d993ecba3991645b4857837277469c8cc4c554a7e24d064d1ca291cfb81f
diff --git a/video/requirements.GPU.txt b/video/requirements.GPU.txt
deleted file mode 100644
index c0322c5..0000000
--- a/video/requirements.GPU.txt
+++ /dev/null
@@ -1,562 +0,0 @@
-colorama==0.4.6 \
- --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
- --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
-coloredlogs==15.0.1 \
- --hash=sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934 \
- --hash=sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0
-cuda-bindings==12.9.4 \
- --hash=sha256:1f53a7f453d4b2643d8663d036bafe29b5ba89eb904c133180f295df6dc151e5 \
- --hash=sha256:20f2699d61d724de3eb3f3369d57e2b245f93085cab44fd37c3bea036cea1a6f \
- --hash=sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f \
- --hash=sha256:37744e721a18a514423e81863f52a4f7f46f5a6f9cccd569f2735f8067f4d8c2 \
- --hash=sha256:3adf4958dcf68ae7801a59b73fb00a8b37f8d0595060d66ceae111b1002de38d \
- --hash=sha256:443b0875916879c2e4c3722941e25e42d5ab9bcbf34c9e83404fb100fa1f6913 \
- --hash=sha256:4d3c842c2a4303b2a580fe955018e31aea30278be19795ae05226235268032e5 \
- --hash=sha256:53a10c71fdbdb743e0268d07964e5a996dd00b4e43831cbfce9804515d97d575 \
- --hash=sha256:53e11991a92ff6f26a0c8a98554cd5d6721c308a6b7bfb08bebac9201e039e43 \
- --hash=sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb \
- --hash=sha256:694ba35023846625ef471257e6b5a4bc8af690f961d197d77d34b1d1db393f56 \
- --hash=sha256:696ca75d249ddf287d01b9a698b8e2d8a05046495a9c051ca15659dc52d17615 \
- --hash=sha256:893ca68114b5b769c1d4c02583b91ed22691887c3ed513b59467d23540104db4 \
- --hash=sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686 \
- --hash=sha256:9866ceec83e39337d1a1d64837864c964ad902992478caa288a0bc1be95f21aa \
- --hash=sha256:a022c96b8bd847e8dc0675523431149a4c3e872f440e3002213dbb9e08f0331a \
- --hash=sha256:a2e82c8985948f953c2be51df45c3fe11c812a928fca525154fb9503190b3e64 \
- --hash=sha256:a6a429dc6c13148ff1e27c44f40a3dd23203823e637b87fd0854205195988306 \
- --hash=sha256:b32d8b685f0e66f5658bcf4601ef034e89fc2843582886f0a58784a4302da06c \
- --hash=sha256:c912a3d9e6b6651853eed8eed96d6800d69c08e94052c292fec3f282c5a817c9 \
- --hash=sha256:cf8bfaedc238f3b115d957d1fd6562b7e8435ba57f6d0e2f87d0e7149ccb2da5 \
- --hash=sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee \
- --hash=sha256:f69107389e6b9948969bfd0a20c4f571fd1aefcfb1d2e1b72cc8ba5ecb7918ab \
- --hash=sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8
-cuda-pathfinder==1.5.1 \
- --hash=sha256:b3718097fb57cf9e8a904dd072d806f2c9a27627e35c020b06ab9454bcec08c0
-cuda-toolkit[cudart]==12.8.1 \
- --hash=sha256:adc7906af4ecbf9a352f9dca5734eceb21daec281ccfcf5675e1d2f724fc2cba
-filelock==3.25.2 \
- --hash=sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694 \
- --hash=sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70
-flatbuffers==25.12.19 \
- --hash=sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4
-fsspec==2026.3.0 \
- --hash=sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41 \
- --hash=sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4
-humanfriendly==10.0 \
- --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \
- --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc
-jinja2==3.1.6 \
- --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
- --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
-markupsafe==3.0.3 \
- --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
- --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
- --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
- --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
- --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
- --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \
- --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
- --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
- --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
- --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
- --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
- --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \
- --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \
- --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
- --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
- --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
- --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \
- --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \
- --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
- --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
- --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
- --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \
- --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \
- --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
- --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
- --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \
- --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
- --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \
- --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
- --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \
- --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
- --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \
- --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
- --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
- --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
- --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
- --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \
- --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
- --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
- --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \
- --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
- --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
- --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
- --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
- --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \
- --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \
- --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
- --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
- --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
- --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
- --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
- --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
- --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \
- --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
- --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
- --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
- --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \
- --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
- --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
- --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
- --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \
- --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
- --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
- --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
- --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \
- --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
- --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \
- --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
- --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \
- --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
- --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
- --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
- --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \
- --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \
- --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \
- --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
- --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \
- --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
- --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
- --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \
- --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
- --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
- --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
- --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
- --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
- --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \
- --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \
- --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
- --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
-ml-dtypes==0.5.4 \
- --hash=sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf \
- --hash=sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d \
- --hash=sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f \
- --hash=sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483 \
- --hash=sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7 \
- --hash=sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22 \
- --hash=sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6 \
- --hash=sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175 \
- --hash=sha256:388d399a2152dd79a3f0456a952284a99ee5c93d3e2f8dfe25977511e0515270 \
- --hash=sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1 \
- --hash=sha256:3d277bf3637f2a62176f4575512e9ff9ef51d00e39626d9fe4a161992f355af2 \
- --hash=sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1 \
- --hash=sha256:4ff7f3e7ca2972e7de850e7b8fcbb355304271e2933dd90814c1cb847414d6e2 \
- --hash=sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298 \
- --hash=sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d \
- --hash=sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de \
- --hash=sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049 \
- --hash=sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d \
- --hash=sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90 \
- --hash=sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb \
- --hash=sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465 \
- --hash=sha256:88c982aac7cb1cbe8cbb4e7f253072b1df872701fcaf48d84ffbb433b6568f24 \
- --hash=sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453 \
- --hash=sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56 \
- --hash=sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48 \
- --hash=sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff \
- --hash=sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460 \
- --hash=sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac \
- --hash=sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900 \
- --hash=sha256:a9b61c19040397970d18d7737375cffd83b1f36a11dd4ad19f83a016f736c3ef \
- --hash=sha256:b4b801ebe0b477be666696bda493a9be8356f1f0057a57f1e35cd26928823e5a \
- --hash=sha256:b95e97e470fe60ed493fd9ae3911d8da4ebac16bd21f87ffa2b7c588bf22ea2c \
- --hash=sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040 \
- --hash=sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9 \
- --hash=sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7 \
- --hash=sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6 \
- --hash=sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b \
- --hash=sha256:d81fdb088defa30eb37bf390bb7dde35d3a83ec112ac8e33d75ab28cc29dd8b0 \
- --hash=sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328
-mpmath==1.3.0 \
- --hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \
- --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
-networkx==3.4.2 \
- --hash=sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1 \
- --hash=sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f
-numpy==2.2.6 \
- --hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \
- --hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \
- --hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \
- --hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
- --hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \
- --hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \
- --hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \
- --hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \
- --hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
- --hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \
- --hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \
- --hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
- --hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \
- --hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \
- --hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \
- --hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
- --hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \
- --hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \
- --hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \
- --hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \
- --hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
- --hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
- --hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \
- --hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
- --hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \
- --hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
- --hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \
- --hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \
- --hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
- --hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
- --hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \
- --hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \
- --hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
- --hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \
- --hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
- --hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
- --hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
- --hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
- --hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
- --hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \
- --hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
- --hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \
- --hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
- --hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
- --hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
- --hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \
- --hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \
- --hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \
- --hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \
- --hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
- --hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \
- --hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \
- --hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \
- --hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \
- --hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8
-nvidia-cublas-cu12==12.8.4.1 \
- --hash=sha256:47e9b82132fa8d2b4944e708049229601448aaad7e6f296f630f2d1a32de35af \
- --hash=sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142 \
- --hash=sha256:b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0
-nvidia-cuda-cupti-cu12==12.8.90 \
- --hash=sha256:4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed \
- --hash=sha256:bb479dcdf7e6d4f8b0b01b115260399bf34154a1a2e9fe11c85c517d87efd98e \
- --hash=sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182
-nvidia-cuda-nvrtc-cu12==12.8.93 \
- --hash=sha256:7a4b6b2904850fe78e0bd179c4b655c404d4bb799ef03ddc60804247099ae909 \
- --hash=sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994 \
- --hash=sha256:fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8
-nvidia-cuda-runtime-cu12==12.8.90 \
- --hash=sha256:52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d \
- --hash=sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90 \
- --hash=sha256:c0c6027f01505bfed6c3b21ec546f69c687689aad5f1a377554bc6ca4aa993a8
-nvidia-cudnn-cu12==9.10.2.21 \
- --hash=sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8 \
- --hash=sha256:c6288de7d63e6cf62988f0923f96dc339cea362decb1bf5b3141883392a7d65e \
- --hash=sha256:c9132cc3f8958447b4910a1720036d9eff5928cc3179b0a51fb6d167c6cc87d8
-nvidia-cufft-cu12==11.3.3.83 \
- --hash=sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74 \
- --hash=sha256:7a64a98ef2a7c47f905aaf8931b69a3a43f27c55530c698bb2ed7c75c0b42cb7 \
- --hash=sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a
-nvidia-cufile-cu12==1.13.1.3 \
- --hash=sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc \
- --hash=sha256:4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a
-nvidia-curand-cu12==10.3.9.90 \
- --hash=sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9 \
- --hash=sha256:dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd \
- --hash=sha256:f149a8ca457277da854f89cf282d6ef43176861926c7ac85b2a0fbd237c587ec
-nvidia-cusolver-cu12==11.7.3.90 \
- --hash=sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450 \
- --hash=sha256:4a550db115fcabc4d495eb7d39ac8b58d4ab5d8e63274d3754df1c0ad6a22d34 \
- --hash=sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0
-nvidia-cusparse-cu12==12.5.8.93 \
- --hash=sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b \
- --hash=sha256:9a33604331cb2cac199f2e7f5104dfbb8a5a898c367a53dfda9ff2acb6b6b4dd \
- --hash=sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc
-nvidia-cusparselt-cu12==0.7.1 \
- --hash=sha256:8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5 \
- --hash=sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623 \
- --hash=sha256:f67fbb5831940ec829c9117b7f33807db9f9678dc2a617fbe781cac17b4e1075
-nvidia-nccl-cu12==2.27.5 \
- --hash=sha256:31432ad4d1fb1004eb0c56203dc9bc2178a1ba69d1d9e02d64a6938ab5e40e7a \
- --hash=sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457
-nvidia-nvjitlink-cu12==12.8.93 \
- --hash=sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88 \
- --hash=sha256:adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7 \
- --hash=sha256:bd93fbeeee850917903583587f4fc3a4eafa022e34572251368238ab5e6bd67f
-nvidia-nvshmem-cu12==3.4.5 \
- --hash=sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd \
- --hash=sha256:0b48363fc6964dede448029434c6abed6c5e37f823cb43c3bcde7ecfc0457e15
-nvidia-nvtx-cu12==12.8.90 \
- --hash=sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f \
- --hash=sha256:619c8304aedc69f02ea82dd244541a83c3d9d40993381b3b590f1adaed3db41e \
- --hash=sha256:d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615
-onnx==1.21.0 \
- --hash=sha256:10c3185a232089335581fabb98fba4e86d3e8246b8140f2e406082438100ebda \
- --hash=sha256:19d9971a3e52a12968ae6c70fd0f86c349536de0b0c33922ecdbe52d1972fe60 \
- --hash=sha256:1a9baf882562c4cebf79589bebb7cd71a20e30b51158cac3e3bbaf27da6163bd \
- --hash=sha256:257d1d1deb6a652913698f1e3f33ef1ca0aa69174892fe38946d4572d89dd94f \
- --hash=sha256:2aca19949260875c14866fc77ea0bc37e4e809b24976108762843d328c92d3ce \
- --hash=sha256:3abd09872523c7e0362d767e4e63bd7c6bac52a5e2c3edbf061061fe540e2027 \
- --hash=sha256:458d91948ad9a7729a347550553b49ab6939f9af2cddf334e2116e45467dc61f \
- --hash=sha256:4d8b67d0aaec5864c87633188b91cc520877477ec0254eda122bef8be43cd764 \
- --hash=sha256:5489f25fe461e7f32128218251a466cabbeeaf1eaa791c79daebf1a80d5a2cc9 \
- --hash=sha256:5f78c411743db317a76e5d009f84f7e3d5380411a1567a868e82461a1e5c775d \
- --hash=sha256:7b58a4cfec8d9311b73dc083e4c1fa362069267881144c05139b3eba5dc3a840 \
- --hash=sha256:7cd7cb8f6459311bdb557cbf6c0ccc6d8ace11c304d1bba0a30b4a4688e245f8 \
- --hash=sha256:7ee9d8fd6a4874a5fa8b44bbcabea104ce752b20469b88bc50c7dcf9030779ad \
- --hash=sha256:82aa6ab51144df07c58c4850cb78d4f1ae969d8c0bf657b28041796d49ba6974 \
- --hash=sha256:9003d5206c01fa2ff4b46311566865d8e493e1a6998d4009ec6de39843f1b59b \
- --hash=sha256:9ea4e824964082811938a9250451d89c4ec474fe42dd36c038bfa5df31993d1e \
- --hash=sha256:a9261bd580fb8548c9c37b3c6750387eb8f21ea43c63880d37b2c622e1684285 \
- --hash=sha256:ab6a488dabbb172eebc9f3b3e7ac68763f32b0c571626d4a5004608f866cc83d \
- --hash=sha256:bba12181566acf49b35875838eba49536a327b2944664b17125577d230c637ad \
- --hash=sha256:c9b56ad04039fac6b028c07e54afa1ec7f75dd340f65311f2c292e41ed7aa4d9 \
- --hash=sha256:ca14bc4842fccc3187eb538f07eabeb25a779b39388b006db4356c07403a7bbb \
- --hash=sha256:db17fc0fec46180b6acbd1d5d8650a04e5527c02b09381da0b5b888d02a204c8 \
- --hash=sha256:e0c21cc5c7a41d1a509828e2b14fe9c30e807c6df611ec0fd64a47b8d4b16abd \
- --hash=sha256:e1931bfcc222a4c9da6475f2ffffb84b97ab3876041ec639171c11ce802bee6a \
- --hash=sha256:efba467efb316baf2a9452d892c2f982b9b758c778d23e38c7f44fa211b30bb9 \
- --hash=sha256:f2c7c234c568402e10db74e33d787e4144e394ae2bcbbf11000fbfe2e017ad68 \
- --hash=sha256:f53b3c15a3b539c16b99655c43c365622046d68c49b680c48eba4da2a4fb6f27 \
- --hash=sha256:fc2635400fe39ff37ebc4e75342cc54450eadadf39c540ff132c319bf4960095
-onnxruntime-gpu==1.23.2 \
- --hash=sha256:054282614c2fc9a4a27d74242afbae706a410f1f63cc35bc72f99709029a5ba4 \
- --hash=sha256:18de50c6c8eea50acc405ea13d299aec593e46478d7a22cd32cdbbdf7c42899d \
- --hash=sha256:1e8f75af5da07329d0c3a5006087f4051d8abd133b4be7c9bae8cdab7bea4c26 \
- --hash=sha256:20959cd4ae358aab6579ab9123284a7b1498f7d51ec291d429a5edc26511306f \
- --hash=sha256:4f2d1f720685d729b5258ec1b36dee1de381b8898189908c98cbeecdb2f2b5c2 \
- --hash=sha256:7f1b3f49e5e126b99e23ec86b4203db41c2a911f6165f7624f2bc8267aaca767 \
- --hash=sha256:d76d1ac7a479ecc3ac54482eea4ba3b10d68e888a0f8b5f420f0bdf82c5eec59 \
- --hash=sha256:deba091e15357355aa836fd64c6c4ac97dd0c4609c38b08a69675073ea46b321 \
- --hash=sha256:fe925a84b00e291e0ad3fac29bfd8f8e06112abc760cdc82cb711b4f3935bd95
-onnxslim==0.1.82 \
- --hash=sha256:3190340f53c93620779f2159b41d114e571b7c1a0cfa8630cba3f7be92d3399e \
- --hash=sha256:4f48decf32863e583976fff6e9cfd9d6fe6a4a9814e7577c2cf8ce082973c6eb
-packaging==26.0 \
- --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
- --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
-pillow==12.2.0 \
- --hash=sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9 \
- --hash=sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5 \
- --hash=sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987 \
- --hash=sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9 \
- --hash=sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b \
- --hash=sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f \
- --hash=sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd \
- --hash=sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e \
- --hash=sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e \
- --hash=sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe \
- --hash=sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795 \
- --hash=sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601 \
- --hash=sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1 \
- --hash=sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed \
- --hash=sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea \
- --hash=sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5 \
- --hash=sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97 \
- --hash=sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453 \
- --hash=sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98 \
- --hash=sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa \
- --hash=sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b \
- --hash=sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d \
- --hash=sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705 \
- --hash=sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8 \
- --hash=sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024 \
- --hash=sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0 \
- --hash=sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286 \
- --hash=sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150 \
- --hash=sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2 \
- --hash=sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3 \
- --hash=sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b \
- --hash=sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f \
- --hash=sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463 \
- --hash=sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940 \
- --hash=sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166 \
- --hash=sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed \
- --hash=sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f \
- --hash=sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795 \
- --hash=sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780 \
- --hash=sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7 \
- --hash=sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1 \
- --hash=sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5 \
- --hash=sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295 \
- --hash=sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b \
- --hash=sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354 \
- --hash=sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60 \
- --hash=sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65 \
- --hash=sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005 \
- --hash=sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c \
- --hash=sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be \
- --hash=sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5 \
- --hash=sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06 \
- --hash=sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae \
- --hash=sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c \
- --hash=sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c \
- --hash=sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612 \
- --hash=sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e \
- --hash=sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab \
- --hash=sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808 \
- --hash=sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f \
- --hash=sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e \
- --hash=sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909 \
- --hash=sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec \
- --hash=sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe \
- --hash=sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50 \
- --hash=sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4 \
- --hash=sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f \
- --hash=sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff \
- --hash=sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5 \
- --hash=sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb \
- --hash=sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414 \
- --hash=sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1 \
- --hash=sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032 \
- --hash=sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76 \
- --hash=sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136 \
- --hash=sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e \
- --hash=sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c \
- --hash=sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3 \
- --hash=sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea \
- --hash=sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f \
- --hash=sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104 \
- --hash=sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176 \
- --hash=sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24 \
- --hash=sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3 \
- --hash=sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4 \
- --hash=sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed \
- --hash=sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43 \
- --hash=sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421 \
- --hash=sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7 \
- --hash=sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06 \
- --hash=sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5
-protobuf==5.29.6 \
- --hash=sha256:36ade6ff88212e91aef4e687a971a11d7d24d6948a66751abc1b3238648f5d05 \
- --hash=sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1 \
- --hash=sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86 \
- --hash=sha256:76e07e6567f8baf827137e8d5b8204b6c7b6488bbbff1bf0a72b383f77999c18 \
- --hash=sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda \
- --hash=sha256:831e2da16b6cc9d8f1654c041dd594eda43391affd3c03a91bea7f7f6da106d6 \
- --hash=sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6 \
- --hash=sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269 \
- --hash=sha256:cb4c86de9cd8a7f3a256b9744220d87b847371c6b2f10bde87768918ef33ba49 \
- --hash=sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723 \
- --hash=sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9
-sympy==1.14.0 \
- --hash=sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517 \
- --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5
-tensorrt-cu12==10.14.1.48.post1 \
- --hash=sha256:5a6d4d78560be7c8fff877711fa8334e8e2b441b702f047ea3107311b9897341
-tensorrt-cu12-bindings==10.14.1.48.post1 \
- --hash=sha256:03bd44f645a30e04f38d4a866bdfb0e9e16a34601384ce29ef8d008950175828 \
- --hash=sha256:1cc29c5bb32a0719d5fadbbd2e69971837b2b0d2f1575d9a2ddc1cf3f6f5d8f0 \
- --hash=sha256:2a0b5a301c84d1c95e67cede52bb49f44fda02a45c6a4b409fa16f0632394046 \
- --hash=sha256:2b83b1608e21c25da72776501533b17fe1d000595b9a191613665f67e0598868 \
- --hash=sha256:40100265c49dc91e0a3f0a030e7de0077c034e6e30c11828001b036052de1d77 \
- --hash=sha256:68ae05d23f4918fdd36a505daf8b93b64444ef9328516284363480ab776a2595 \
- --hash=sha256:78da2abb803370147e75045eaeaa2a3f134f5aa7537405f86b22eaa36c0a11ed \
- --hash=sha256:9b2c8f41d847b202e35054fbaccd55f4efbb673da11f562befc273c0b2d65f48 \
- --hash=sha256:aad15bf393acc85b2e5015f0ca2082b0d162d5966ac1f58de48d1205446e237f \
- --hash=sha256:b2bf5597c7790c36fa858b8dfd6a867a482ee41d7c35e8732b3fd671b013869c \
- --hash=sha256:b90ce26abe1d49da527211411d023f95a235806fab2d00277585558e265f9b93 \
- --hash=sha256:d9cb40e646e11225b295eaeaf74aeb7e422c425271d51ca8c416776449fec617
-tensorrt-cu12-libs==10.14.1.48.post1 \
- --hash=sha256:46e9e84e16ca7d89ca572e0900d9480945bb6faaa0c385e6f63e1ae46a834b25
-torch==2.10.0 \
- --hash=sha256:0228d20b06701c05a8f978357f657817a4a63984b0c90745def81c18aedfa591 \
- --hash=sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574 \
- --hash=sha256:233aed0659a2503b831d8a67e9da66a62c996204c0bba4f4c442ccc0c68a3f60 \
- --hash=sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd \
- --hash=sha256:2b980edd8d7c0a68c4e951ee1856334a43193f98730d97408fbd148c1a933313 \
- --hash=sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547 \
- --hash=sha256:3202429f58309b9fa96a614885eace4b7995729f44beb54d3e4a47773649d382 \
- --hash=sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4 \
- --hash=sha256:35e407430795c8d3edb07a1d711c41cc1f9eaddc8b2f1cc0a165a6767a8fb73d \
- --hash=sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f \
- --hash=sha256:46b3574d93a2a8134b3f5475cfb98e2eb46771794c57015f6ad1fb795ec25e49 \
- --hash=sha256:5276fa790a666ee8becaffff8acb711922252521b28fbce5db7db5cf9cb2026d \
- --hash=sha256:5c4d217b14741e40776dd7074d9006fd28b8a97ef5654db959d8635b2fe5f29b \
- --hash=sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf \
- --hash=sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f \
- --hash=sha256:6528f13d2a8593a1a412ea07a99812495bec07e9224c28b2a25c0a30c7da025c \
- --hash=sha256:682497e16bdfa6efeec8cde66531bc8d1fbbbb4d8788ec6173c089ed3cc2bfe5 \
- --hash=sha256:6b71486353fce0f9714ca0c9ef1c850a2ae766b409808acd58e9678a3edb7738 \
- --hash=sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294 \
- --hash=sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185 \
- --hash=sha256:716b01a176c2a5659c98f6b01bf868244abdd896526f1c692712ab36dbaf9b63 \
- --hash=sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb \
- --hash=sha256:80b1b5bfe38eb0e9f5ff09f206dcac0a87aadd084230d4a36eea5ec5232c115b \
- --hash=sha256:98c01b8bb5e3240426dcde1446eed6f40c778091c8544767ef1168fc663a05a6 \
- --hash=sha256:a1ff626b884f8c4e897c4c33782bdacdff842a165fee79817b1dd549fdda1321 \
- --hash=sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763 \
- --hash=sha256:a4be6a2a190b32ff5c8002a0977a25ea60e64f7ba46b1be37093c141d9c49aeb \
- --hash=sha256:aae1b29cd68e50a9397f5ee897b9c24742e9e306f88a807a27d617f07adb3bd8 \
- --hash=sha256:aaf663927bcd490ae971469a624c322202a2a1e68936eb952535ca4cd3b90444 \
- --hash=sha256:ac5bdcbb074384c66fa160c15b1ead77839e3fe7ed117d667249afce0acabfac \
- --hash=sha256:b1d5e2aba4eb7f8e87fbe04f86442887f9167a35f092afe4c237dfcaaef6e328 \
- --hash=sha256:b7bd80f3477b830dd166c707c5b0b82a898e7b16f59a7d9d42778dd058272e8b \
- --hash=sha256:bf0d9ff448b0218e0433aeb198805192346c4fd659c852370d5cc245f602a06a \
- --hash=sha256:c2ee399c644dc92ef7bc0d4f7e74b5360c37cdbe7c5ba11318dda49ffac2bc57 \
- --hash=sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f \
- --hash=sha256:d8f5912ba938233f86361e891789595ff35ca4b4e2ac8fe3670895e5976731d6 \
- --hash=sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e \
- --hash=sha256:f5ab4ba32383061be0fb74bda772d470140a12c1c3b58a0cfbf3dae94d164c28 \
- --hash=sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8
-torchvision==0.25.0 \
- --hash=sha256:0b5e7f50002a8145a98c5694a018e738c50e2972608310c7e88e1bd4c058f6ce \
- --hash=sha256:0d9a3f925a081dd2ebb0b791249b687c2ef2c2717d027946654607494b9b64b6 \
- --hash=sha256:146d02c9876858420adf41f3189fe90e3d6a409cbfa65454c09f25fb33bf7266 \
- --hash=sha256:153c0d2cbc34b7cf2da19d73450f24ba36d2b75ec9211b9962b5022fb9e4ecee \
- --hash=sha256:24e11199e4d84ba9c5ee7825ebdf1cd37ce8deec225117f10243cae984ced3ec \
- --hash=sha256:40a122c3cf4d14b651f095e0f672b688dde78632783fc5cd3d4d5e4f6a828563 \
- --hash=sha256:5e6b449e9fa7d642142c0e27c41e5a43b508d57ed8e79b7c0a0c28652da8678c \
- --hash=sha256:5f271136d2d2c0b7a24c5671795c6e4fd8da4e0ea98aeb1041f62bc04c4370ef \
- --hash=sha256:620a236288d594dcec7634c754484542dc0a5c1b0e0b83a34bda5e91e9b7c3a1 \
- --hash=sha256:632db02300e83793812eee4f61ae6a2686dab10b4cfd628b620dc47747aa9d03 \
- --hash=sha256:846890161b825b38aa85fc37fb3ba5eea74e7091ff28bab378287111483b6443 \
- --hash=sha256:855c0dc6d37f462482da7531c6788518baedca1e0847f3df42a911713acdfe52 \
- --hash=sha256:a8f8061284395ce31bcd460f2169013382ccf411148ceb2ee38e718e9860f5a7 \
- --hash=sha256:a95c47abb817d4e90ea1a8e57bd0d728e3e6b533b3495ae77d84d883c4d11f56 \
- --hash=sha256:acc339aba4a858192998c2b91f635827e40d9c469d9cf1455bafdda6e4c28ea4 \
- --hash=sha256:ad9a8a5877782944d99186e4502a614770fe906626d76e9cd32446a0ac3075f2 \
- --hash=sha256:b57430fbe9e9b697418a395041bb615124d9c007710a2712fda6e35fb310f264 \
- --hash=sha256:b75deafa2dfea3e2c2a525559b04783515e3463f6e830cb71de0fb7ea36fe233 \
- --hash=sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b \
- --hash=sha256:c4d395cb2c4a2712f6eb93a34476cdf7aae74bb6ea2ea1917f858e96344b00aa \
- --hash=sha256:cef0196be31be421f6f462d1e9da1101be7332d91984caa6f8022e6c78a5877f \
- --hash=sha256:d1abd5ed030c708f5dbf4812ad5f6fbe9384b63c40d6bd79f8df41a4a759a917 \
- --hash=sha256:db74a551946b75d19f9996c419a799ffdf6a223ecf17c656f90da011f1d75b20 \
- --hash=sha256:ea580ffd6094cc01914ad32f8c8118174f18974629af905cea08cb6d5d48c7b7 \
- --hash=sha256:f07f01d27375ad89d72aa2b3f2180f07da95dd9d2e4c758e015c0acb2da72977 \
- --hash=sha256:f25aa9e380865b11ea6e9d99d84df86b9cc959f1a007cd966fc6f1ab2ed0e248 \
- --hash=sha256:f49964f96644dbac2506dffe1a0a7ec0f2bf8cf7a588c3319fed26e6329ffdf3 \
- --hash=sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3
-triton==3.6.0 \
- --hash=sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d \
- --hash=sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9 \
- --hash=sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6 \
- --hash=sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4 \
- --hash=sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd \
- --hash=sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7 \
- --hash=sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651 \
- --hash=sha256:6c723cfb12f6842a0ae94ac307dba7e7a44741d720a40cf0e270ed4a4e3be781 \
- --hash=sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca \
- --hash=sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803 \
- --hash=sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea \
- --hash=sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f \
- --hash=sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3 \
- --hash=sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43
-typing-extensions==4.15.0 \
- --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
- --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
-vdms==0.0.23 \
- --hash=sha256:2dec153f8cb33f27cdb9ab33125198195fd29a6777cf890fc735774ed9327874 \
- --hash=sha256:7cd93242df644947c009559f31b4e19eb1dab6b62ab783ce5a2a54a4ad392f57
-
-# The following packages are considered to be unsafe in a requirements file:
-pip==26.0.1 \
- --hash=sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b \
- --hash=sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8
diff --git a/video/requirements.txt b/video/requirements.txt
index 8784d50..5bb247f 100644
--- a/video/requirements.txt
+++ b/video/requirements.txt
@@ -1,740 +1,151 @@
build==1.4.0 \
--hash=sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 \
--hash=sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936
-certifi==2026.1.4 \
- --hash=sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c \
- --hash=sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120
-charset-normalizer==3.4.4 \
- --hash=sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad \
- --hash=sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93 \
- --hash=sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394 \
- --hash=sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89 \
- --hash=sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc \
- --hash=sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86 \
- --hash=sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63 \
- --hash=sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d \
- --hash=sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f \
- --hash=sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8 \
- --hash=sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0 \
- --hash=sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505 \
- --hash=sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161 \
- --hash=sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af \
- --hash=sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152 \
- --hash=sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318 \
- --hash=sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72 \
- --hash=sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4 \
- --hash=sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e \
- --hash=sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3 \
- --hash=sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576 \
- --hash=sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c \
- --hash=sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1 \
- --hash=sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8 \
- --hash=sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1 \
- --hash=sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2 \
- --hash=sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44 \
- --hash=sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26 \
- --hash=sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88 \
- --hash=sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 \
- --hash=sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede \
- --hash=sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf \
- --hash=sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a \
- --hash=sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc \
- --hash=sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0 \
- --hash=sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84 \
- --hash=sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db \
- --hash=sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1 \
- --hash=sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7 \
- --hash=sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed \
- --hash=sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 \
- --hash=sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133 \
- --hash=sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e \
- --hash=sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef \
- --hash=sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14 \
- --hash=sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2 \
- --hash=sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0 \
- --hash=sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d \
- --hash=sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828 \
- --hash=sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f \
- --hash=sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf \
- --hash=sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6 \
- --hash=sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328 \
- --hash=sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090 \
- --hash=sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa \
- --hash=sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 \
- --hash=sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c \
- --hash=sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb \
- --hash=sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc \
- --hash=sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a \
- --hash=sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec \
- --hash=sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc \
- --hash=sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac \
- --hash=sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e \
- --hash=sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313 \
- --hash=sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569 \
- --hash=sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3 \
- --hash=sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d \
- --hash=sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525 \
- --hash=sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 \
- --hash=sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3 \
- --hash=sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9 \
- --hash=sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a \
- --hash=sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9 \
- --hash=sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 \
- --hash=sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25 \
- --hash=sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50 \
- --hash=sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf \
- --hash=sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1 \
- --hash=sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3 \
- --hash=sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac \
- --hash=sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e \
- --hash=sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815 \
- --hash=sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c \
- --hash=sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6 \
- --hash=sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6 \
- --hash=sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e \
- --hash=sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4 \
- --hash=sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84 \
- --hash=sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69 \
- --hash=sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15 \
- --hash=sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191 \
- --hash=sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0 \
- --hash=sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897 \
- --hash=sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd \
- --hash=sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2 \
- --hash=sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 \
- --hash=sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d \
- --hash=sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074 \
- --hash=sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3 \
- --hash=sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224 \
- --hash=sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838 \
- --hash=sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a \
- --hash=sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d \
- --hash=sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d \
- --hash=sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f \
- --hash=sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8 \
- --hash=sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490 \
- --hash=sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966 \
- --hash=sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9 \
- --hash=sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3 \
- --hash=sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e \
- --hash=sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608
-contourpy==1.3.2 \
- --hash=sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f \
- --hash=sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92 \
- --hash=sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16 \
- --hash=sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f \
- --hash=sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f \
- --hash=sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7 \
- --hash=sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e \
- --hash=sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08 \
- --hash=sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841 \
- --hash=sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5 \
- --hash=sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2 \
- --hash=sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415 \
- --hash=sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878 \
- --hash=sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0 \
- --hash=sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab \
- --hash=sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445 \
- --hash=sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43 \
- --hash=sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c \
- --hash=sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823 \
- --hash=sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69 \
- --hash=sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15 \
- --hash=sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef \
- --hash=sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5 \
- --hash=sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73 \
- --hash=sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9 \
- --hash=sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912 \
- --hash=sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5 \
- --hash=sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85 \
- --hash=sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d \
- --hash=sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631 \
- --hash=sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2 \
- --hash=sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54 \
- --hash=sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773 \
- --hash=sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934 \
- --hash=sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a \
- --hash=sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441 \
- --hash=sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422 \
- --hash=sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532 \
- --hash=sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739 \
- --hash=sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b \
- --hash=sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f \
- --hash=sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1 \
- --hash=sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87 \
- --hash=sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52 \
- --hash=sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1 \
- --hash=sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd \
- --hash=sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989 \
- --hash=sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb \
- --hash=sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f \
- --hash=sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad \
- --hash=sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9 \
- --hash=sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512 \
- --hash=sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd \
- --hash=sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83 \
- --hash=sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe \
- --hash=sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0 \
- --hash=sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c
-cycler==0.12.1 \
- --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \
- --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c
-defusedxml==0.7.1 \
- --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \
- --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
-filelock==3.20.3 \
- --hash=sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1 \
- --hash=sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1
-fonttools==4.61.1 \
- --hash=sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87 \
- --hash=sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796 \
- --hash=sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75 \
- --hash=sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d \
- --hash=sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371 \
- --hash=sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b \
- --hash=sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b \
- --hash=sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2 \
- --hash=sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3 \
- --hash=sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9 \
- --hash=sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd \
- --hash=sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c \
- --hash=sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c \
- --hash=sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56 \
- --hash=sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37 \
- --hash=sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0 \
- --hash=sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958 \
- --hash=sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5 \
- --hash=sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118 \
- --hash=sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69 \
- --hash=sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9 \
- --hash=sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261 \
- --hash=sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb \
- --hash=sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47 \
- --hash=sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24 \
- --hash=sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c \
- --hash=sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba \
- --hash=sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c \
- --hash=sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91 \
- --hash=sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1 \
- --hash=sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19 \
- --hash=sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6 \
- --hash=sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5 \
- --hash=sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2 \
- --hash=sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d \
- --hash=sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881 \
- --hash=sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063 \
- --hash=sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7 \
- --hash=sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09 \
- --hash=sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da \
- --hash=sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e \
- --hash=sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e \
- --hash=sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8 \
- --hash=sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa \
- --hash=sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6 \
- --hash=sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e \
- --hash=sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a \
- --hash=sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c \
- --hash=sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7 \
- --hash=sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd
-fsspec==2026.1.0 \
- --hash=sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc \
- --hash=sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b
+certifi==2026.2.25 \
+ --hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \
+ --hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7
+charset-normalizer==3.4.6 \
+ --hash=sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e \
+ --hash=sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c \
+ --hash=sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5 \
+ --hash=sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815 \
+ --hash=sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f \
+ --hash=sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0 \
+ --hash=sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484 \
+ --hash=sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407 \
+ --hash=sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6 \
+ --hash=sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8 \
+ --hash=sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264 \
+ --hash=sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815 \
+ --hash=sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2 \
+ --hash=sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4 \
+ --hash=sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579 \
+ --hash=sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f \
+ --hash=sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa \
+ --hash=sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95 \
+ --hash=sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab \
+ --hash=sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297 \
+ --hash=sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a \
+ --hash=sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e \
+ --hash=sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84 \
+ --hash=sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8 \
+ --hash=sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0 \
+ --hash=sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9 \
+ --hash=sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f \
+ --hash=sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1 \
+ --hash=sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843 \
+ --hash=sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565 \
+ --hash=sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7 \
+ --hash=sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c \
+ --hash=sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b \
+ --hash=sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7 \
+ --hash=sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687 \
+ --hash=sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9 \
+ --hash=sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14 \
+ --hash=sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89 \
+ --hash=sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f \
+ --hash=sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0 \
+ --hash=sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9 \
+ --hash=sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a \
+ --hash=sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389 \
+ --hash=sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0 \
+ --hash=sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30 \
+ --hash=sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd \
+ --hash=sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e \
+ --hash=sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9 \
+ --hash=sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc \
+ --hash=sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532 \
+ --hash=sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d \
+ --hash=sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae \
+ --hash=sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2 \
+ --hash=sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64 \
+ --hash=sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f \
+ --hash=sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557 \
+ --hash=sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e \
+ --hash=sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff \
+ --hash=sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398 \
+ --hash=sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db \
+ --hash=sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a \
+ --hash=sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43 \
+ --hash=sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597 \
+ --hash=sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c \
+ --hash=sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e \
+ --hash=sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2 \
+ --hash=sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54 \
+ --hash=sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e \
+ --hash=sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4 \
+ --hash=sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4 \
+ --hash=sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7 \
+ --hash=sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6 \
+ --hash=sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5 \
+ --hash=sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194 \
+ --hash=sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69 \
+ --hash=sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f \
+ --hash=sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316 \
+ --hash=sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e \
+ --hash=sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73 \
+ --hash=sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8 \
+ --hash=sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923 \
+ --hash=sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88 \
+ --hash=sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f \
+ --hash=sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21 \
+ --hash=sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4 \
+ --hash=sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6 \
+ --hash=sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc \
+ --hash=sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2 \
+ --hash=sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866 \
+ --hash=sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021 \
+ --hash=sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2 \
+ --hash=sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d \
+ --hash=sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8 \
+ --hash=sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de \
+ --hash=sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237 \
+ --hash=sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4 \
+ --hash=sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778 \
+ --hash=sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb \
+ --hash=sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc \
+ --hash=sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602 \
+ --hash=sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4 \
+ --hash=sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f \
+ --hash=sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5 \
+ --hash=sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611 \
+ --hash=sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8 \
+ --hash=sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf \
+ --hash=sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d \
+ --hash=sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b \
+ --hash=sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db \
+ --hash=sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e \
+ --hash=sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077 \
+ --hash=sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd \
+ --hash=sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef \
+ --hash=sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e \
+ --hash=sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8 \
+ --hash=sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe \
+ --hash=sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058 \
+ --hash=sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17 \
+ --hash=sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833 \
+ --hash=sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421 \
+ --hash=sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550 \
+ --hash=sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff \
+ --hash=sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2 \
+ --hash=sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc \
+ --hash=sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982 \
+ --hash=sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d \
+ --hash=sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed \
+ --hash=sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104 \
+ --hash=sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659
idna==3.11 \
--hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \
--hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902
inotify==0.2.12 \
--hash=sha256:9aee407f92c7d51a2ce50f3b78291a9094e334e34bd68e82bf60020795fa2c94 \
--hash=sha256:e4f1c8ec7ba5ec2a1a7fce48c0c917234af9d756495ebae7ffa00e41a305ab90
-jinja2==3.1.6 \
- --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
- --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
-kiwisolver==1.4.9 \
- --hash=sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c \
- --hash=sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7 \
- --hash=sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21 \
- --hash=sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e \
- --hash=sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff \
- --hash=sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7 \
- --hash=sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c \
- --hash=sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26 \
- --hash=sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa \
- --hash=sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f \
- --hash=sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1 \
- --hash=sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891 \
- --hash=sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77 \
- --hash=sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543 \
- --hash=sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d \
- --hash=sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce \
- --hash=sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3 \
- --hash=sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60 \
- --hash=sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a \
- --hash=sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089 \
- --hash=sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab \
- --hash=sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78 \
- --hash=sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771 \
- --hash=sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f \
- --hash=sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b \
- --hash=sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14 \
- --hash=sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32 \
- --hash=sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527 \
- --hash=sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185 \
- --hash=sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634 \
- --hash=sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed \
- --hash=sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1 \
- --hash=sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c \
- --hash=sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11 \
- --hash=sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752 \
- --hash=sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5 \
- --hash=sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4 \
- --hash=sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58 \
- --hash=sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5 \
- --hash=sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198 \
- --hash=sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536 \
- --hash=sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134 \
- --hash=sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf \
- --hash=sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2 \
- --hash=sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2 \
- --hash=sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370 \
- --hash=sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1 \
- --hash=sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154 \
- --hash=sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b \
- --hash=sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197 \
- --hash=sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386 \
- --hash=sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a \
- --hash=sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48 \
- --hash=sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748 \
- --hash=sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c \
- --hash=sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8 \
- --hash=sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5 \
- --hash=sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999 \
- --hash=sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369 \
- --hash=sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122 \
- --hash=sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b \
- --hash=sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 \
- --hash=sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9 \
- --hash=sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f \
- --hash=sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799 \
- --hash=sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028 \
- --hash=sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2 \
- --hash=sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525 \
- --hash=sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d \
- --hash=sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb \
- --hash=sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872 \
- --hash=sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64 \
- --hash=sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586 \
- --hash=sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf \
- --hash=sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552 \
- --hash=sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2 \
- --hash=sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415 \
- --hash=sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c \
- --hash=sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6 \
- --hash=sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64 \
- --hash=sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d \
- --hash=sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548 \
- --hash=sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07 \
- --hash=sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61 \
- --hash=sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d \
- --hash=sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771 \
- --hash=sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9 \
- --hash=sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c \
- --hash=sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3 \
- --hash=sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16 \
- --hash=sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145 \
- --hash=sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611 \
- --hash=sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2 \
- --hash=sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464 \
- --hash=sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2 \
- --hash=sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04 \
- --hash=sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54 \
- --hash=sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df \
- --hash=sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f \
- --hash=sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1 \
- --hash=sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220
-markupsafe==3.0.3 \
- --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
- --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
- --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
- --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
- --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
- --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \
- --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
- --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
- --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
- --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
- --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
- --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \
- --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \
- --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
- --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
- --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
- --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \
- --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \
- --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
- --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
- --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
- --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \
- --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \
- --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
- --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
- --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \
- --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
- --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \
- --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
- --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \
- --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
- --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \
- --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
- --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
- --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
- --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
- --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \
- --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
- --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
- --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \
- --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
- --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
- --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
- --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
- --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \
- --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \
- --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
- --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
- --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
- --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
- --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
- --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
- --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \
- --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
- --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
- --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
- --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \
- --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
- --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
- --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
- --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \
- --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
- --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
- --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
- --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \
- --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
- --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \
- --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
- --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \
- --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
- --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
- --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
- --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \
- --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \
- --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \
- --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
- --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \
- --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
- --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
- --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \
- --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
- --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
- --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
- --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
- --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
- --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \
- --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \
- --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
- --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
-matplotlib==3.10.8 \
- --hash=sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7 \
- --hash=sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a \
- --hash=sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f \
- --hash=sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3 \
- --hash=sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5 \
- --hash=sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9 \
- --hash=sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2 \
- --hash=sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3 \
- --hash=sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6 \
- --hash=sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f \
- --hash=sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b \
- --hash=sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8 \
- --hash=sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008 \
- --hash=sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b \
- --hash=sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656 \
- --hash=sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958 \
- --hash=sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04 \
- --hash=sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b \
- --hash=sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6 \
- --hash=sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908 \
- --hash=sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c \
- --hash=sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1 \
- --hash=sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d \
- --hash=sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1 \
- --hash=sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c \
- --hash=sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a \
- --hash=sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce \
- --hash=sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a \
- --hash=sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160 \
- --hash=sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1 \
- --hash=sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11 \
- --hash=sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a \
- --hash=sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466 \
- --hash=sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486 \
- --hash=sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78 \
- --hash=sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17 \
- --hash=sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077 \
- --hash=sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565 \
- --hash=sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f \
- --hash=sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50 \
- --hash=sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58 \
- --hash=sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2 \
- --hash=sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645 \
- --hash=sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2 \
- --hash=sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39 \
- --hash=sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf \
- --hash=sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149 \
- --hash=sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22 \
- --hash=sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df \
- --hash=sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4 \
- --hash=sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933 \
- --hash=sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6 \
- --hash=sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8 \
- --hash=sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a \
- --hash=sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7
-mpmath==1.3.0 \
- --hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \
- --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
-networkx==3.1 \
- --hash=sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36 \
- --hash=sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61
-numpy==1.26.4 \
- --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \
- --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \
- --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \
- --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \
- --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \
- --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \
- --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \
- --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \
- --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \
- --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \
- --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \
- --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \
- --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \
- --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \
- --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \
- --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \
- --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \
- --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \
- --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \
- --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \
- --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \
- --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \
- --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \
- --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \
- --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \
- --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \
- --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \
- --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \
- --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \
- --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \
- --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \
- --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \
- --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \
- --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \
- --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \
- --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f
-opencv-python==4.11.0.86 \
- --hash=sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4 \
- --hash=sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec \
- --hash=sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202 \
- --hash=sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a \
- --hash=sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d \
- --hash=sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b \
- --hash=sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66
-opencv-python-headless==4.11.0.86 \
- --hash=sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b \
- --hash=sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca \
- --hash=sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca \
- --hash=sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb \
- --hash=sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798 \
- --hash=sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81 \
- --hash=sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b
-openvino==2024.6.0 \
- --hash=sha256:05a436e2526bf6775b487712ab4a6decf7afe04183109bc7b129b95205ec4247 \
- --hash=sha256:151c6010f0613ace29087bf864dc5dd1db0afa25ab87379c735f85500545b167 \
- --hash=sha256:17cbdae3b069d9a13389b43a15b521a86fef75f350cfd0d992cc1780a6123788 \
- --hash=sha256:1d59ccc2df4a8c8515f30683f6cc0091c5b4e6a01a71ebfb1dc8f2ebdb5c83f4 \
- --hash=sha256:263496653200270b8a17456dc7bc67a198ed29b601b0ad7280d90783ba918d66 \
- --hash=sha256:2acfe89f61ec18d608c79147cedea6ba85d80783723ab9a9416891b74b15785e \
- --hash=sha256:444a6d3a746dd728654877993c30582faefcad2868c80ec261839a144b8925bc \
- --hash=sha256:46d3e20838c918668ec10ae124e92d56ff47a7c14da0d9d19d01d91fc9dece94 \
- --hash=sha256:4a94ef27022d56a159ea8677e5561bdd6f0d6c6237736e13c0f7cde71224ce7f \
- --hash=sha256:89c866fa87ddeccb982252e7d1c8516101a4acb95aaebac7e43f450112cb6942 \
- --hash=sha256:8d0390f4ab9b8f27814cf0feeddb0bba449ae8cd99933c2c2fae3011d33973b3 \
- --hash=sha256:9150c75e19606a2f2ab411271c79031726980fb6870b96445db1faca5ab64f5f \
- --hash=sha256:989c803d1f8e6d12ee8d2e3e8c7d56189d96688433b2238270dbfbf64f163f18 \
- --hash=sha256:ac812efa9c139387db534498a1e980103a35b9b2b5f3b2d1ae6ed43db51b4bba \
- --hash=sha256:c25d6820e960c08c0cc8d7823e9e6e395d762bc603772af14f0a576fab1628eb \
- --hash=sha256:c3477e833541df4f316240491d9df157c20a81487991748eb33e02e534009999 \
- --hash=sha256:c6c6a40a890b51007b20cf97d81783c4b43edfbcd4aabb06b6b04b7f2fb3137f \
- --hash=sha256:cb94f4ca9c1857bda2c5b510e519db20ea246958dbd76ddc38c133e2216231a5 \
- --hash=sha256:f7177d9a734a61a0708bcd706592f59fc2377d8efbb0bf9f8a33edfd2998a45d \
- --hash=sha256:ff5f871162299906fb517d5c9ded79f464a0341d8e2d55a037d4f9e38eed8652
-openvino-dev==2024.6.0 \
- --hash=sha256:b8f4a1baeea1b138bc9b75b53dc3bdad3307c2cd4d58526ad977df81635870c5
-openvino-telemetry==2025.2.0 \
- --hash=sha256:8bf8127218e51e99547bf38b8fb85a8b31c9bf96e6f3a82eb0b3b6a34155977c \
- --hash=sha256:bcb667e83a44f202ecf4cfa49281715c6d7e21499daec04ff853b7f964833599
packaging==26.0 \
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
-pillow==12.2.0 \
- --hash=sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9 \
- --hash=sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5 \
- --hash=sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987 \
- --hash=sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9 \
- --hash=sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b \
- --hash=sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f \
- --hash=sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd \
- --hash=sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e \
- --hash=sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e \
- --hash=sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe \
- --hash=sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795 \
- --hash=sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601 \
- --hash=sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1 \
- --hash=sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed \
- --hash=sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea \
- --hash=sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5 \
- --hash=sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97 \
- --hash=sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453 \
- --hash=sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98 \
- --hash=sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa \
- --hash=sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b \
- --hash=sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d \
- --hash=sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705 \
- --hash=sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8 \
- --hash=sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024 \
- --hash=sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0 \
- --hash=sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286 \
- --hash=sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150 \
- --hash=sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2 \
- --hash=sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3 \
- --hash=sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b \
- --hash=sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f \
- --hash=sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463 \
- --hash=sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940 \
- --hash=sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166 \
- --hash=sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed \
- --hash=sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f \
- --hash=sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795 \
- --hash=sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780 \
- --hash=sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7 \
- --hash=sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1 \
- --hash=sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5 \
- --hash=sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295 \
- --hash=sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b \
- --hash=sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354 \
- --hash=sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60 \
- --hash=sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65 \
- --hash=sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005 \
- --hash=sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c \
- --hash=sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be \
- --hash=sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5 \
- --hash=sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06 \
- --hash=sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae \
- --hash=sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c \
- --hash=sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c \
- --hash=sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612 \
- --hash=sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e \
- --hash=sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab \
- --hash=sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808 \
- --hash=sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f \
- --hash=sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e \
- --hash=sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909 \
- --hash=sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec \
- --hash=sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe \
- --hash=sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50 \
- --hash=sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4 \
- --hash=sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f \
- --hash=sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff \
- --hash=sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5 \
- --hash=sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb \
- --hash=sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414 \
- --hash=sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1 \
- --hash=sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032 \
- --hash=sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76 \
- --hash=sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136 \
- --hash=sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e \
- --hash=sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c \
- --hash=sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3 \
- --hash=sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea \
- --hash=sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f \
- --hash=sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104 \
- --hash=sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176 \
- --hash=sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24 \
- --hash=sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3 \
- --hash=sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4 \
- --hash=sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed \
- --hash=sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43 \
- --hash=sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421 \
- --hash=sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7 \
- --hash=sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06 \
- --hash=sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5
-pip==26.0.1 \
- --hash=sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b \
- --hash=sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8
-polars==1.37.1 \
- --hash=sha256:0309e2a4633e712513401964b4d95452f124ceabf7aec6db50affb9ced4a274e \
- --hash=sha256:377fed8939a2f1223c1563cfabdc7b4a3d6ff846efa1f2ddeb8644fafd9b1aff
-polars-runtime-32==1.37.1 \
- --hash=sha256:04f5d5a2f013dca7391b7d8e7672fa6d37573a87f1d45d3dd5f0d9b5565a4b0f \
- --hash=sha256:0b8d4d73ea9977d3731927740e59d814647c5198bdbe359bcf6a8bfce2e79771 \
- --hash=sha256:55f2c4847a8d2e267612f564de7b753a4bde3902eaabe7b436a0a4abf75949a0 \
- --hash=sha256:68779d4a691da20a5eb767d74165a8f80a2bdfbde4b54acf59af43f7fa028d8f \
- --hash=sha256:a8362d11ac5193b994c7e9048ffe22ccfb976699cfbf6e128ce0302e06728894 \
- --hash=sha256:c682bf83f5f352e5e02f5c16c652c48ca40442f07b236f30662b22217320ce76 \
- --hash=sha256:da3d3642ae944e18dd17109d2a3036cb94ce50e5495c5023c77b1599d4c861bc \
- --hash=sha256:fbfde7c0ca8209eeaed546e4a32cca1319189aa61c5f0f9a2b4494262bd0c689 \
- --hash=sha256:fc82b5bbe70ca1a4b764eed1419f6336752d6ba9fc1245388d7f8b12438afa2c
-protobuf==5.29.6 \
- --hash=sha256:36ade6ff88212e91aef4e687a971a11d7d24d6948a66751abc1b3238648f5d05 \
- --hash=sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1 \
- --hash=sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86 \
- --hash=sha256:76e07e6567f8baf827137e8d5b8204b6c7b6488bbbff1bf0a72b383f77999c18 \
- --hash=sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda \
- --hash=sha256:831e2da16b6cc9d8f1654c041dd594eda43391affd3c03a91bea7f7f6da106d6 \
- --hash=sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6 \
- --hash=sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269 \
- --hash=sha256:cb4c86de9cd8a7f3a256b9744220d87b847371c6b2f10bde87768918ef33ba49 \
- --hash=sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723 \
- --hash=sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9
-psutil==7.2.1 \
- --hash=sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1 \
- --hash=sha256:08a2f175e48a898c8eb8eace45ce01777f4785bc744c90aa2cc7f2fa5462a266 \
- --hash=sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442 \
- --hash=sha256:2ceae842a78d1603753561132d5ad1b2f8a7979cb0c283f5b52fb4e6e14b1a79 \
- --hash=sha256:35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf \
- --hash=sha256:3fce5f92c22b00cdefd1645aa58ab4877a01679e901555067b1bd77039aa589f \
- --hash=sha256:494c513ccc53225ae23eec7fe6e1482f1b8a44674241b54561f755a898650679 \
- --hash=sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8 \
- --hash=sha256:81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49 \
- --hash=sha256:923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f \
- --hash=sha256:93f3f7b0bb07711b49626e7940d6fe52aa9940ad86e8f7e74842e73189712129 \
- --hash=sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67 \
- --hash=sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6 \
- --hash=sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17 \
- --hash=sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42 \
- --hash=sha256:ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d \
- --hash=sha256:cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672 \
- --hash=sha256:d34d2ca888208eea2b5c68186841336a7f5e0b990edec929be909353a202768a \
- --hash=sha256:ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc \
- --hash=sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3 \
- --hash=sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8
-pyparsing==3.3.2 \
- --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
- --hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
pyproject-hooks==1.2.0 \
--hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \
--hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913
-python-dateutil==2.9.0.post0 \
- --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
- --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
pyyaml==6.0.3 \
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
@@ -812,123 +223,54 @@ pyyaml==6.0.3 \
requests==2.33.1 \
--hash=sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517 \
--hash=sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a
-scipy==1.15.3 \
- --hash=sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477 \
- --hash=sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c \
- --hash=sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723 \
- --hash=sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730 \
- --hash=sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539 \
- --hash=sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb \
- --hash=sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6 \
- --hash=sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594 \
- --hash=sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92 \
- --hash=sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82 \
- --hash=sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49 \
- --hash=sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759 \
- --hash=sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba \
- --hash=sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982 \
- --hash=sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8 \
- --hash=sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65 \
- --hash=sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4 \
- --hash=sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e \
- --hash=sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed \
- --hash=sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c \
- --hash=sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5 \
- --hash=sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5 \
- --hash=sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019 \
- --hash=sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e \
- --hash=sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1 \
- --hash=sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889 \
- --hash=sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca \
- --hash=sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825 \
- --hash=sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9 \
- --hash=sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62 \
- --hash=sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb \
- --hash=sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b \
- --hash=sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13 \
- --hash=sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb \
- --hash=sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40 \
- --hash=sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c \
- --hash=sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253 \
- --hash=sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb \
- --hash=sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f \
- --hash=sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163 \
- --hash=sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45 \
- --hash=sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7 \
- --hash=sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11 \
- --hash=sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf \
- --hash=sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e \
- --hash=sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126
-six==1.17.0 \
- --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
- --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
-sympy==1.14.0 \
- --hash=sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517 \
- --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5
-tomli==1.1.0 \
- --hash=sha256:33d7984738f8bb699c9b0a816eb646a8178a69eaa792d258486776a5d21b8ca5 \
- --hash=sha256:f4a182048010e89cbec0ae4686b21f550a7f2903f665e34a6de58ec15424f919
-torch==2.10.0+cpu \
- --hash=sha256:0e286fcf6ce0cc7b204396c9b4ea0d375f1f0c3e752f68ce3d3aeb265511db8c \
- --hash=sha256:0fedcb1a77e8f2aaf7bfd21591bf6d1e0b207473268c9be16b17cb7783253969 \
- --hash=sha256:106dd1930cb30a4a337366ba3f9b25318ebf940f51fd46f789281dd9e736bdc4 \
- --hash=sha256:170a0623108055be5199370335cf9b41ba6875b3cb6f086db4aee583331a4899 \
- --hash=sha256:179451716487f8cb09b56459667fa1f5c4c0946c1e75fbeae77cfc40a5768d87 \
- --hash=sha256:17a09465bab2aab8f0f273410297133d8d8fb6dd84dccbd252ca4a4f3a111847 \
- --hash=sha256:1cfcb9b1558c6e52dffd0d4effce83b13c5ae5d97338164c372048c21f9cfccb \
- --hash=sha256:21cb5436978ef47c823b7a813ff0f8c2892e266cfe0f1d944879b5fba81bf4e1 \
- --hash=sha256:23882f8d882460aca809882fc42f5e343bf07585274f929ced00177d1be1eb67 \
- --hash=sha256:2adc71fe471e98a608723bfc837f7e1929885ebb912c693597711e139c1cda41 \
- --hash=sha256:31ae44836c8b9bbd1a3943d29c7c7457709ddf7c6173aa34aefe9d2203e4c405 \
- --hash=sha256:3eaa727e6a73affa61564d86b9d03191df45c8650d0666bd3d57c8597ef61e78 \
- --hash=sha256:4fcd8b4cc2ae20f2b7749fb275349c55432393868778c2d50a08e81d5ee5591e \
- --hash=sha256:5af75e5f49de21b0bdf7672bc27139bd285f9e8dbcabe2d617a2eb656514ac36 \
- --hash=sha256:6c6f0df770144907092a0d067048d96ed4f278a6c840376d2ff0e27e7579b925 \
- --hash=sha256:8d316e5bf121f1eab1147e49ad0511a9d92e4c45cc357d1ab0bee440da71a095 \
- --hash=sha256:8de5a36371b775e2d4881ed12cc7f2de400b1ad3d728aa74a281f649f87c9b8c \
- --hash=sha256:9412bd37b70f5ebd1205242c4ba4cabae35a605947f2b30806d5c9b467936db9 \
- --hash=sha256:9accc30b56cb6756d4a9d04fcb8ebc0bb68c7d55c1ed31a8657397d316d31596 \
- --hash=sha256:a280ffaea7b9c828e0c1b9b3bd502d9b6a649dc9416997b69b84544bd469f215 \
- --hash=sha256:a28fdbcfa2fbacffec81300f24dd1bed2b0ccfdbed107a823cff12bc1db070f6 \
- --hash=sha256:aada8afc068add586464b2a55adb7cc9091eec55caf5320447204741cb6a0604 \
- --hash=sha256:b67d91326e4ed9eccbd6b7d84ed7ffa43f93103aa3f0b24145f3001f3b11b714 \
- --hash=sha256:b719da5af01b59126ac13eefd6ba3dd12d002dc0e8e79b8b365e55267a8189d3 \
- --hash=sha256:b7cb1ec66cefb90fd7b676eac72cfda3b8d4e4d0cacd7a531963bc2e0a9710ab \
- --hash=sha256:ba51ef01a510baf8fff576174f702c47e1aa54389a9f1fba323bb1a5003ff0bf \
- --hash=sha256:beadc2a6a1785b09a46daad378de91ef274b8d3eea7af0bc2d017d97f115afdf \
- --hash=sha256:c35c0de592941d4944698dbfa87271ab85d3370eca3b694943a2ab307ac34b3f \
- --hash=sha256:ce5c113d1f55f8c1f5af05047a24e50d11d293e0cbbb5bf7a75c6c761edd6eaa \
- --hash=sha256:d63ee6a80982fd73fe44bb70d97d2976e010312ff6db81d7bfb9167b06dd45b9 \
- --hash=sha256:e51994492cdb76edce29da88de3672a3022f9ef0ffd90345436948d4992be2c7 \
- --hash=sha256:e71c476517c33e7db69825a9ff46c7f47a723ec4dac5b2481cff4246d1c632be \
- --hash=sha256:ea2bcc9d1fca66974a71d4bf9a502539283f35d61fcab5a799b4e120846f1e02 \
- --hash=sha256:eb1bde1ce198f05c8770017de27e001d404499cf552aaaa014569eff56ca25c0 \
- --hash=sha256:ee40b8a4b4b2cf0670c6fd4f35a7ef23871af956fecb238fbf5da15a72650b1d \
- --hash=sha256:f8294fd2fc6dd8f4435a891a0122307a043b14b21f0dac1bca63c85bfb59e586 \
- --hash=sha256:fd215f3d0f681905c5b56b0630a3d666900a37fcc3ca5b937f95275c66f9fd9c \
- --hash=sha256:ffc8da9a1341092d6a90cb5b1c1a33cd61abf0fb43f0cd88443c27fa372c26ae
-torchvision==0.25.0+cpu \
- --hash=sha256:0c2d0da9bc011a0fde1d125af396a8fbe94d99becf9d313764f24ca7657a3448 \
- --hash=sha256:2d444009c0956669ada149f61ed78f257c1cc96d259efa6acf3929ca96ceb3f0 \
- --hash=sha256:3e2ae9981e32a5b9db685659d5c7af0f04b159ff20394650a90124baf6ada51a \
- --hash=sha256:499eae1e535766391b6ee2d1e6e841239c20e2e6d88203a15b8f9f8d60a1f8bd \
- --hash=sha256:4d72a57a8f0b5146e26dac1fbfa2c905280cd04f5fcb23b9c56253506b683aeb \
- --hash=sha256:59be99d1c470ef470b134468aa6afa6f968081a503acb4ee883d70332f822e35 \
- --hash=sha256:727334e9a721cfc1ac296ce0bf9e69d9486821bfa5b1e75a8feb6f78041db481 \
- --hash=sha256:73ce04dea64914ff1110008311204c366107d651d3b04faa0dbee77efb7133b7 \
- --hash=sha256:783c8fc580bbfc159bff52f4f72cdd538e42b32956e70dffa42b940db114e151 \
- --hash=sha256:7d47d544899fabac52ebe0d4812975608fd7ab79a3d7fb6383275eb667e33f53 \
- --hash=sha256:7f851245a2687743742157988ed9c42c6e312b95bbe6cfcac9e7d0d0c28ae62f \
- --hash=sha256:813f0106eb3e268f3783da67b882458e544c6fb72f946e6ca64b5ed4e62c6a77 \
- --hash=sha256:90eec299e1f82cfaf080ccb789df3838cb9a54b57e2ebe33852cd392c692de5c \
- --hash=sha256:9212210f417888e6261c040495180f053084812cf873dedba9fc51ff4b24b2d3 \
- --hash=sha256:9511339b3b5eb75229e0b5041202e8aed9bef3b1de3a715b9fb319c9e97688fd \
- --hash=sha256:aa016ab73e06a886f72edc8929ed2ed4c85aaaa6e10500ecdef921b03129b19e \
- --hash=sha256:c1be164e93c68b2dbf460fd58975377c892dbcf3358fb72941709c3857351bba \
- --hash=sha256:c7eb5f219fdfaf1f65e68c00eb81172ab4fa08a9874dae9dad2bca360da34d0f \
- --hash=sha256:e985e12a9a232618e5a43476de5689e4b14989f5da6b93909c57afa57ec27012 \
- --hash=sha256:fb9f07f6a10f0ac24ac482ae68c6df99110b74a0d80a4c64fddc9753267d8815 \
- --hash=sha256:fe54cbd5942cd0b26a90f1748f0d4421caf67be35c281c6c3b8573733a03d630
+tomli==2.4.0 \
+ --hash=sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729 \
+ --hash=sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b \
+ --hash=sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d \
+ --hash=sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df \
+ --hash=sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576 \
+ --hash=sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d \
+ --hash=sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1 \
+ --hash=sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a \
+ --hash=sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e \
+ --hash=sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc \
+ --hash=sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702 \
+ --hash=sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6 \
+ --hash=sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd \
+ --hash=sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4 \
+ --hash=sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776 \
+ --hash=sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a \
+ --hash=sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66 \
+ --hash=sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87 \
+ --hash=sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2 \
+ --hash=sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f \
+ --hash=sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475 \
+ --hash=sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f \
+ --hash=sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95 \
+ --hash=sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9 \
+ --hash=sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3 \
+ --hash=sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9 \
+ --hash=sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76 \
+ --hash=sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da \
+ --hash=sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8 \
+ --hash=sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51 \
+ --hash=sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86 \
+ --hash=sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8 \
+ --hash=sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0 \
+ --hash=sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b \
+ --hash=sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1 \
+ --hash=sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e \
+ --hash=sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d \
+ --hash=sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c \
+ --hash=sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867 \
+ --hash=sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a \
+ --hash=sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c \
+ --hash=sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0 \
+ --hash=sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4 \
+ --hash=sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614 \
+ --hash=sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132 \
+ --hash=sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa \
+ --hash=sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087
tornado==6.5.5 \
--hash=sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9 \
--hash=sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6 \
@@ -940,21 +282,11 @@ tornado==6.5.5 \
--hash=sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521 \
--hash=sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7 \
--hash=sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5
-typing-extensions==4.15.0 \
- --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
- --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
-ultralytics==8.4.34 \
- --hash=sha256:e932da39976879ddf4f398993bd44587c1f5aafaac138efab38d34030680c113 \
- --hash=sha256:fe94bf945c2c69b4df245ee9327dc3c7ce6d06ec9c647843331b0d497a48db78
-ultralytics-thop==2.0.18 \
- --hash=sha256:21103bcd39cc9928477dc3d9374561749b66a1781b35f46256c8d8c4ac01d9cf \
- --hash=sha256:2bb44851ad224b116c3995b02dd5e474a5ccf00acf237fe0edb9e1506ede04ec
urllib3==2.6.3 \
--hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \
--hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4
-vdms==0.0.23 \
- --hash=sha256:2dec153f8cb33f27cdb9ab33125198195fd29a6777cf890fc735774ed9327874 \
- --hash=sha256:7cd93242df644947c009559f31b4e19eb1dab6b62ab783ce5a2a54a4ad392f57
-wheel==0.46.3 \
- --hash=sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d \
- --hash=sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803
+
+# The following packages are considered to be unsafe in a requirements file:
+pip==26.0.1 \
+ --hash=sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b \
+ --hash=sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8
diff --git a/video/resources/models/download_yolo.py b/video/resources/models/download_yolo.py
deleted file mode 100644
index 9dae0b5..0000000
--- a/video/resources/models/download_yolo.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import os
-from pathlib import Path
-
-from ultralytics import YOLO
-
-
-def str2bool(in_val):
- if isinstance(in_val, bool):
- return in_val
-
- if not isinstance(in_val, str):
- raise ValueError(f"{in_val} is not a bool or string")
-
- if in_val.title() == "True":
- return True
- else:
- return False
-
-
-model_precision_object = "FP16"
-half_flag = True
-dynamic_flag = True
-CUSTOM_MODEL_FLAG = str2bool(os.getenv("CUSTOM_MODEL_FLAG", False))
-DEVICE = os.environ.get("DEVICE", "CPU")
-MODEL_NAME = os.environ.get("MODEL_NAME", "yolo11n")
-if DEVICE == "GPU":
- batch_size = int(os.environ.get("GPU_BATCH_SIZE", 1))
- run_platform_name = "engine"
- os.environ["CUDA_VISIBLE_DEVICES"] = "0"
- print("[!] USING GPU & TENSORRT")
-else:
- # batch_size = 8
- batch_size = int(os.environ.get("CPU_BATCH_SIZE", 1)) # 8
- run_platform_name = "openvino"
- print("[!] USING CPU & OPENVINO")
-
-
-def get_model(model_dir, run_platform, device_input, batch=1):
- final_model_path = f"{model_dir}/{MODEL_NAME}.pt"
- pt_detection_model = YOLO(final_model_path, verbose=False, task="detect")
- if run_platform == "openvino":
- pt_detection_model.export(
- format="openvino",
- half=half_flag,
- dynamic=dynamic_flag,
- device=device_input,
- batch=batch,
- )
-
- final_model_path = f"{model_dir}/{MODEL_NAME}_openvino_model/"
- object_detection_model = YOLO(
- final_model_path,
- verbose=False,
- task="detect",
- )
-
- # det_ov_model = core.read_model(final_model_path+"yolo11n.xml")
- # ov_config = {hints.performance_mode: hints.PerformanceMode.LATENCY}
- # if device == "GPU":
- # ov_config["GPU_DISABLE_WINOGRAD_CONVOLUTION"] = "YES"
- # compiled_model = core.compile_model(det_ov_model, device, ov_config)
- # object_detection_model.predictor.model.ov_compiled_model = compiled_model
-
- elif run_platform == "engine":
- pt_detection_model.export(
- format="engine",
- half=half_flag,
- imgsz=[7680, 4320], # Max dimensions (8K-[W,H]-[7680,4320])
- dynamic=dynamic_flag,
- device=device_input,
- simplify=True,
- batch=batch,
- )
-
- final_model_path = f"{model_dir}/{MODEL_NAME}.engine"
- object_detection_model = YOLO(
- final_model_path,
- verbose=False,
- task="detect",
- )
-
- elif run_platform == "onnx":
- from torch import cuda
- from ultralytics.utils.checks import check_requirements
-
- check_requirements(
- "onnxruntime-gpu"
- if cuda.is_available() and device_input != "cpu"
- else "onnxruntime"
- )
-
- final_model_path = f"{model_dir}/{MODEL_NAME}.onnx"
- pt_detection_model.export(
- format="onnx",
- half=half_flag,
- dynamic=dynamic_flag,
- device=device_input,
- simplify=True,
- batch=batch,
- )
-
- object_detection_model = YOLO(final_model_path, verbose=False, task="detect")
-
- elif run_platform == "pytorch":
- object_detection_model = pt_detection_model
- if DEVICE == "GPU":
- object_detection_model.to("cuda")
- else:
- object_detection_model.to(device_input)
-
- else:
- raise ValueError(f"[!] Model for {run_platform} is not implemented.")
-
- return object_detection_model, final_model_path
-
-
-if __name__ == "__main__":
- if CUSTOM_MODEL_FLAG:
- dir_path = "/home/resources/models/ultralytics/custom_models"
- else:
- dir_path = (
- f"/home/resources/models/ultralytics/{MODEL_NAME}/{model_precision_object}"
- )
-
- ydir = Path(dir_path)
-
- device_input = DEVICE.lower() if DEVICE == "CPU" else "cuda"
- _, _ = get_model(ydir, run_platform_name, device_input, batch=batch_size)
-
- os.remove(f"{ydir}/{MODEL_NAME}.pt")
diff --git a/video/segment.py b/video/segment.py
index dc5a7d8..6cedf16 100755
--- a/video/segment.py
+++ b/video/segment.py
@@ -7,7 +7,14 @@
from tornado import gen, web
from tornado.concurrent import run_on_executor
-from utils import safely_join_path
+
+
+def safely_join_path(base_dir, add_path):
+ safe_base = os.path.abspath(base_dir)
+ candidate_path = os.path.abspath(os.path.join(safe_base, add_path))
+ if not candidate_path.startswith(safe_base + os.sep):
+ raise ValueError(f"Invalid path: {candidate_path}")
+ return candidate_path
class SegmentHandler(web.RequestHandler):
diff --git a/video/source_watcher.py b/video/source_watcher.py
new file mode 100644
index 0000000..9abcac3
--- /dev/null
+++ b/video/source_watcher.py
@@ -0,0 +1,317 @@
+import logging
+import multiprocessing as mp
+import os
+import sys
+import time
+import traceback
+from multiprocessing.managers import BaseManager
+from pathlib import Path
+
+import requests
+import yaml
+from inotify.adapters import Inotify
+
+logger = logging.getLogger(__name__)
+
+BACKEND_URL = os.getenv("BACKEND_URL", "http://fastapi-service:8000")
+DEBUG = os.environ["DEBUG"]
+DEBUG_FLAG = True if DEBUG == "1" else False
+ACCEPTED_VIDEO_FORMATS = [".mp4", ".mkv", ".avi"]
+num_workers = mp.cpu_count() // 2
+empty_timeout = 5 * 60
+
+
+# --- HELPER FUNCTIONS ---
+def is_file_ready(filepath, retries=5, delay=1):
+ """
+ Checks if a file is fully written and accessible.
+ Uses exponential backoff for retries.
+ """
+ for _ in range(retries):
+ try:
+ # Try to open the file in append mode to check for locks
+ with open(filepath, "r"):
+ return True
+ except (IOError, OSError):
+ logger.warning(f"File {filepath} is locked. Retrying in {delay}s...")
+ time.sleep(delay)
+ delay *= 2 # Exponential backoff
+ return False
+
+
+def connect_to_app():
+ # is_ready = False
+ # while not is_ready:
+ with requests.Session() as session:
+ while True:
+ try:
+ res = session.get(f"{BACKEND_URL}/status")
+
+ if res.status_code == 200:
+ # Expected format: {"cam_1": {"status": "Ready"}, ...}
+ data = res.json()
+
+ is_ready = any(status == "Ready" for status in data.values())
+ if is_ready:
+ break
+ except requests.exceptions.RequestException:
+ logger.info("BACKEND: Waiting for fastapi-service ...")
+
+ time.sleep(1)
+
+
+# --- WATCHERS & MANAGERS ---
+class QueueManager(BaseManager):
+ pass
+
+
+def worker_process(queue):
+ """Consumes tasks from the queue and sends them to the backend."""
+ while True:
+ try:
+ # Wait for a task; timeout prevents hanging on shutdown
+ task = queue.get(timeout=10)
+ if task is None:
+ break # Sentinel value to stop worker
+
+ source, camera_name = task
+ start_stream_processor(source, camera_name)
+ except mp.queues.Empty:
+ continue
+ except Exception as e:
+ logger.error(f"Worker error: {e}")
+
+
+def watch_video_files(queue, watch_dir):
+ # Initial scan of files
+ with os.scandir(watch_dir) as entries:
+ for entry in entries:
+ # entry.is_file() and entry.name are retrieved in one go
+ if entry.is_file() and any(
+ entry.name.endswith(ext) for ext in ACCEPTED_VIDEO_FORMATS
+ ):
+ source = entry.path # Full path is already available
+
+ if is_file_ready(source):
+ queue.put((source, None))
+ logger.info(f"{source} added to queue: {time.time()}")
+ # start_stream_processor(source, None)
+
+ # Watch watch_dir for new files
+ i = Inotify()
+ i.add_watch(watch_dir)
+ logger.info("START ADDING VIDEO FILES TO WATCHED DIRECTORY")
+
+ for event in i.event_gen(yield_nones=False):
+ (_, type_names, path, filename) = event
+
+ # New file created in watched directory
+ # IN_CLOSE_WRITE is better than IN_CREATE because it triggers
+ # only after the writing process finishes and closes the file.
+ if "IN_CLOSE_WRITE" in type_names and any(
+ filename.endswith(ext) for ext in ACCEPTED_VIDEO_FORMATS
+ ):
+ source = os.path.join(path, filename)
+
+ if is_file_ready(source):
+ queue.put((source, None))
+ logger.info(f"{source} added to queue: {time.time()}")
+ # start_stream_processor(source, None)
+ else:
+ logger.error(f"Failed to access {source} after retries. Skipping.")
+
+
+def retrieve_camera_details(queue, config_path):
+ """
+ Watches config file and adds only new streams (via camera_name) to queue
+ """
+ unique_camera_names = set()
+
+ def read_config_and_queue():
+ try:
+ with open(config_path, "r") as inFile:
+ config = yaml.safe_load(inFile)
+
+ if config:
+ for camera_name, camera_details in config.items():
+ # run_processor(camera_details["url"], camera_name=camera_name)
+ if isinstance(camera_details, dict) and "url" in camera_details:
+ source = camera_details["url"]
+ if source not in unique_camera_names:
+ logger.info(f"{source} added to queue: {time.time()}")
+ queue.put((source, camera_name))
+ unique_camera_names.add(source)
+ # start_stream_processor(source, camera_name)
+ else:
+ logger.warning(f"Invalid entry: {camera_name}")
+ except Exception:
+ e = traceback.format_exc()
+ logger.info(f"Unexpected error processing config file: {e}")
+
+ # Initial scan of config file
+ read_config_and_queue()
+
+ # Watch for changes to file
+ i = Inotify()
+ config_dir = os.path.dirname(os.path.abspath(config_path))
+ i.add_watch(config_dir)
+
+ for event in i.event_gen(yield_nones=False):
+ (_, type_names, path, filename) = event
+
+ if filename == os.path.basename(config_path) and "IN_CLOSE_WRITE" in type_names:
+ time.sleep(0.5)
+ read_config_and_queue()
+
+
+def unified_watcher(queue, watch_dir, config_path):
+ """
+ Watches directory for both videos and config changes.
+ """
+ unique_camera_names = set()
+ config_filename = os.path.basename(config_path)
+
+ def read_config():
+ try:
+ with open(config_path, "r") as f:
+ config = yaml.safe_load(f)
+ if config:
+ for name, details in config.items():
+ url = details.get("url")
+ if url and url not in unique_camera_names:
+ logger.info(f"CAMERA: {name} added to queue: {time.time()}")
+ queue.put((url, name))
+ unique_camera_names.add(url)
+ except Exception as e:
+ logger.error(f"CONFIG ERROR: {e}")
+
+ # Initial scan of config file
+ read_config()
+
+ # Initial scan of video files
+ with os.scandir(watch_dir) as entries:
+ for entry in entries:
+ if entry.is_file() and any(
+ entry.name.endswith(ext) for ext in ACCEPTED_VIDEO_FORMATS
+ ):
+ if is_file_ready(entry.path):
+ queue.put((entry.path, None))
+
+ # Unified Event Loop
+ i = Inotify()
+ i.add_watch(watch_dir)
+ logger.info(f"UNIFIED WATCHER: Monitoring {watch_dir} for videos and config...")
+
+ for event in i.event_gen(yield_nones=False):
+ (_, type_names, path, filename) = event
+ full_path = os.path.join(path, filename)
+
+ # Handle Config Update (Supports Atomic Saves/Moves)
+ if filename == config_filename:
+ if any(ev in type_names for ev in ["IN_CLOSE_WRITE", "IN_MOVED_TO"]):
+ time.sleep(0.2)
+ read_config()
+
+ # Handle New Video Files
+ elif any(filename.endswith(ext) for ext in ACCEPTED_VIDEO_FORMATS):
+ if "IN_CLOSE_WRITE" in type_names:
+ if is_file_ready(full_path):
+ queue.put((full_path, None))
+ logger.info(f"VIDEO: {full_path} added to queue: {time.time()}")
+
+
+# --- STREAMER ---
+def start_stream_processor(source, camera_name):
+ if camera_name is None:
+ camera_name = Path(source).stem
+
+ payload = {"url": str(source), "name": camera_name}
+
+ for attempt in range(1, 11):
+ try:
+ # FastAPI stream_processor
+ res = requests.post(f"{BACKEND_URL}/stream", json=payload)
+
+ # Raise an exception for 4xx or 5xx status codes
+ res.raise_for_status()
+
+ if res.status_code == 200:
+ logger.info(f"Started {source} process on attempt {attempt}.")
+ return res
+
+ except requests.exceptions.HTTPError as e:
+ # Handle specific API errors (e.g., 400 Bad Request, 500 Internal Server Error)
+ logger.info(f"HTTP Error: {e.response.status_code} - {e.response.text}")
+
+ # If it's a client error (4xx), retrying likely won't help
+ if 400 <= e.response.status_code < 500:
+ break
+
+ except requests.exceptions.RequestException as e:
+ # Handle connection issues, timeouts, and DNS errors
+ logger.info(f"Connection attempt {attempt} failed: {e}")
+
+ except Exception:
+ e = traceback.format_exc()
+ logger.info(f"Unexpected error processing {source}: {e}")
+
+ time.sleep(1)
+
+
+# --- MAIN FUNCTION ---
+def main(watch_folder=os.getcwd()):
+ # Wait until App is ready
+ connect_to_app()
+
+ if DEBUG_FLAG:
+ logger.info("[TIMING],start_watchandsend,," + str(time.time()))
+
+ # Setup the Shared Queue and Manager
+ file_queue = mp.Queue()
+ QueueManager.register("get_file_queue", callable=lambda: file_queue)
+
+ # Start the manager on port 5005
+ manager = QueueManager(address=("0.0.0.0", 5005), authkey=b"password123")
+ manager.start()
+ logger.info("Queue Server started at 0.0.0.0:5005")
+
+ # Define worker processes
+ processes = [
+ mp.Process( # Process that retrieves camera info from config file (added to file_queue)
+ target=unified_watcher,
+ args=(file_queue, watch_folder, "/home/camera_config.yaml"),
+ name="UnifiedWatcher",
+ daemon=True,
+ ),
+ ]
+
+ for _ in range(num_workers):
+ p = mp.Process(target=worker_process, args=(file_queue,), daemon=True)
+ processes.append(p)
+
+ # 4. Start processed and Keep the server alive
+ try:
+ # Start all processes
+ for p in processes:
+ p.start()
+ while True:
+ time.sleep(1)
+ except KeyboardInterrupt:
+ logger.info("Stopping services ...")
+ finally:
+ # Cleanup
+ for p in processes:
+ if p.is_alive():
+ p.terminate()
+ p.join() # Ensure fully closed
+
+ manager.shutdown()
+ if DEBUG_FLAG:
+ logger.info("[TIMING],end_watchandsend,," + str(time.time()))
+
+
+if __name__ == "__main__":
+ if len(sys.argv) == 2:
+ main(sys.argv[1])
+ else:
+ raise ValueError("Invalid input. Please provide watch directory.")
diff --git a/video/thumbnail.py b/video/thumbnail.py
index 9a0d236..342e054 100755
--- a/video/thumbnail.py
+++ b/video/thumbnail.py
@@ -7,7 +7,14 @@
from tornado import gen, web
from tornado.concurrent import run_on_executor
-from utils import safely_join_path
+
+
+def safely_join_path(base_dir, add_path):
+ safe_base = os.path.abspath(base_dir)
+ candidate_path = os.path.abspath(os.path.join(safe_base, add_path))
+ if not candidate_path.startswith(safe_base + os.sep):
+ raise ValueError(f"Invalid path: {candidate_path}")
+ return candidate_path
class ThumbnailHandler(web.RequestHandler):
diff --git a/video/watch_and_send2vdms.py b/video/watch_and_send2vdms.py
deleted file mode 100644
index 2bdc3b8..0000000
--- a/video/watch_and_send2vdms.py
+++ /dev/null
@@ -1,115 +0,0 @@
-import multiprocessing as mp
-import os
-import queue
-import subprocess
-import sys
-import time
-
-import yaml
-from inotify.adapters import Inotify
-
-num_workers = mp.cpu_count() // 2
-
-DEBUG = os.environ["DEBUG"]
-DEBUG_FLAG = True if DEBUG == "1" else False
-
-
-def run_processor(path_or_url, camera_name=None):
- cmd = [
- sys.executable,
- "/home/process_stream.py",
- path_or_url,
- # camera_name,
- ]
- if camera_name is not None:
- cmd.append(camera_name)
-
- subprocess.run(cmd, check=True)
-
-
-def watch_video_files(queue, watch_dir):
- # Get files already in watch_dir
- for filename in os.listdir(watch_dir):
- if any(filename.endswith(ext) for ext in [".mp4", ".mkv", ".avi"]):
- source = os.path.join(watch_dir, filename)
- print(f"{source} added to queue: {time.time()}", flush=True)
- queue.put((source, None))
-
- # Watch watch_dir for new files
- i = Inotify()
- i.add_watch(watch_dir)
- print("START ADDING VIDEO FILES TO WATCHED DIRECTORY", flush=True)
-
- for event in i.event_gen(yield_nones=False):
- (_, type_names, path, filename) = event
-
- # New file created in watched directory
- # if "IN_CREATE" in type_names:
- if "IN_CLOSE_WRITE" in type_names and any(
- filename.endswith(ext) for ext in [".mp4", ".mkv", ".avi"]
- ):
- source = os.path.join(path, filename)
- print(f"{source} added to queue: {time.time()}", flush=True)
- queue.put((source, None))
-
-
-def retrieve_camera_details(queue, config_path):
- config = None
- with open(config_path, "r") as inFile:
- config = yaml.safe_load(inFile)
-
- if config is not None:
- for camera_name, camera_details in config.items():
- # run_processor(camera_details["url"], camera_name=camera_name)
- source = camera_details["url"]
- print(f"{source} added to queue: {time.time()}", flush=True)
- queue.put((source, camera_name))
-
-
-def main(watch_folder=os.getcwd()):
- if DEBUG_FLAG:
- print("[TIMING],start_watchandsend,," + str(time.time()), flush=True)
-
- file_queue = mp.Queue()
-
- # Create a process that monitors new files in watch_folder (added to file_queue)
- watcher_process = mp.Process(
- target=watch_video_files, args=(file_queue, watch_folder)
- )
-
- # Create a process that retrieves camera info from config file (added to file_queue)
- watcher_process_camera = mp.Process(
- target=retrieve_camera_details, args=(file_queue, "/home/camera_config.yaml")
- )
-
- watcher_process.start()
- watcher_process_camera.start()
-
- # Pool of workers to process video clips
- with mp.Pool(processes=num_workers) as pool:
- while True:
- try:
- path_or_url, camera_name = file_queue.get(timeout=0.5)
- # pool.apply(run_processor, (path_or_url, camera_name,))
- pool.apply_async(
- run_processor,
- (
- path_or_url,
- camera_name,
- ),
- )
- except queue.Empty:
- pass
-
- watcher_process.join()
- watcher_process_camera.join()
-
- if DEBUG_FLAG:
- print("[TIMING],end_watchandsend,," + str(time.time()), flush=True)
-
-
-if __name__ == "__main__":
- if len(sys.argv) == 2:
- main(sys.argv[1])
- else:
- raise ValueError("Invalid input. Please provide watch directory.")