mirror of
https://github.com/ruvnet/RuView
synced 2026-07-28 18:21:42 +00:00
chore(repo): move v1/ → archive/v1/ + add archive/README.md (#430)
The Rust port at v2/ has been the primary codebase since the rename in #427. The Python implementation at v1/ is no longer the active target; the only load-bearing path is the deterministic proof bundle at v1/data/proof/ (per ADR-011 / ADR-028 witness verification). Move the whole Python tree into archive/v1/ and document the policy in archive/README.md: no new features, bug fixes only when they affect a still-load-bearing path (currently just the proof), CI continues to verify the proof on every push and PR. Path references updated in 26 files via path-pattern sed (only matches v1/<known-child> patterns, never bare v1 or API URLs like /api/v1/). Two double-prefix typos (archive/archive/v1/) caught and hand-fixed in verify-pipeline.yml and ADR-011. Validated: - Python proof verify.py imports cleanly at archive/v1/data/proof/ (numpy/scipy still required; CI installs requirements-lock.txt from archive/v1/ now) - cargo test --workspace --no-default-features → 1,539 passed, 0 failed, 8 ignored (unaffected by Python tree relocation) - ESP32-S3 on COM7 untouched (no firmware paths changed) After-merge: contributors should re-run any local `python v1/...` commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already updated).
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Testing utilities for WiFi-DensePose.
|
||||
|
||||
This module contains mock data generators and testing helpers that are
|
||||
ONLY intended for use in development/testing environments. These generators
|
||||
produce synthetic data that mimics real CSI and pose data patterns.
|
||||
|
||||
WARNING: Code in this module uses random number generation intentionally
|
||||
for mock/test data. Do NOT import from this module in production code paths
|
||||
unless behind an explicit mock_mode flag with appropriate logging.
|
||||
"""
|
||||
|
||||
from .mock_csi_generator import MockCSIGenerator
|
||||
from .mock_pose_generator import generate_mock_poses, generate_mock_keypoints, generate_mock_bounding_box
|
||||
|
||||
__all__ = [
|
||||
"MockCSIGenerator",
|
||||
"generate_mock_poses",
|
||||
"generate_mock_keypoints",
|
||||
"generate_mock_bounding_box",
|
||||
]
|
||||
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
Mock CSI data generator for testing and development.
|
||||
|
||||
This module provides synthetic CSI (Channel State Information) data generation
|
||||
for use in development and testing environments ONLY. The generated data mimics
|
||||
realistic WiFi CSI patterns including multipath effects, human motion signatures,
|
||||
and noise characteristics.
|
||||
|
||||
WARNING: This module uses np.random intentionally for test data generation.
|
||||
Do NOT use this module in production data paths.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Banner displayed when mock mode is active
|
||||
MOCK_MODE_BANNER = """
|
||||
================================================================================
|
||||
WARNING: MOCK MODE ACTIVE - Using synthetic CSI data
|
||||
|
||||
All CSI data is randomly generated and does NOT represent real WiFi signals.
|
||||
For real pose estimation, configure hardware per docs/hardware-setup.md.
|
||||
================================================================================
|
||||
"""
|
||||
|
||||
|
||||
class MockCSIGenerator:
|
||||
"""Generator for synthetic CSI data used in testing and development.
|
||||
|
||||
This class produces complex-valued CSI matrices that simulate realistic
|
||||
WiFi channel characteristics including:
|
||||
- Per-antenna and per-subcarrier amplitude/phase variation
|
||||
- Simulated human movement signatures
|
||||
- Configurable noise levels
|
||||
- Temporal coherence across consecutive frames
|
||||
|
||||
This is ONLY for testing. Production code must use real hardware data.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_subcarriers: int = 64,
|
||||
num_antennas: int = 4,
|
||||
num_samples: int = 100,
|
||||
noise_level: float = 0.1,
|
||||
movement_freq: float = 0.5,
|
||||
movement_amplitude: float = 0.3,
|
||||
):
|
||||
"""Initialize mock CSI generator.
|
||||
|
||||
Args:
|
||||
num_subcarriers: Number of OFDM subcarriers to simulate
|
||||
num_antennas: Number of antenna elements
|
||||
num_samples: Number of temporal samples per frame
|
||||
noise_level: Standard deviation of additive Gaussian noise
|
||||
movement_freq: Frequency of simulated human movement (Hz)
|
||||
movement_amplitude: Amplitude of movement-induced CSI variation
|
||||
"""
|
||||
self.num_subcarriers = num_subcarriers
|
||||
self.num_antennas = num_antennas
|
||||
self.num_samples = num_samples
|
||||
self.noise_level = noise_level
|
||||
self.movement_freq = movement_freq
|
||||
self.movement_amplitude = movement_amplitude
|
||||
|
||||
# Internal state for temporal coherence
|
||||
self._phase = 0.0
|
||||
self._frequency = 0.1
|
||||
self._amplitude_base = 1.0
|
||||
|
||||
self._banner_shown = False
|
||||
|
||||
def show_banner(self) -> None:
|
||||
"""Display the mock mode warning banner (once per session)."""
|
||||
if not self._banner_shown:
|
||||
logger.warning(MOCK_MODE_BANNER)
|
||||
self._banner_shown = True
|
||||
|
||||
def generate(self) -> np.ndarray:
|
||||
"""Generate a single frame of mock CSI data.
|
||||
|
||||
Returns:
|
||||
Complex-valued numpy array of shape
|
||||
(num_antennas, num_subcarriers, num_samples).
|
||||
"""
|
||||
self.show_banner()
|
||||
|
||||
# Advance internal phase for temporal coherence
|
||||
self._phase += self._frequency
|
||||
|
||||
time_axis = np.linspace(0, 1, self.num_samples)
|
||||
|
||||
csi_data = np.zeros(
|
||||
(self.num_antennas, self.num_subcarriers, self.num_samples),
|
||||
dtype=complex,
|
||||
)
|
||||
|
||||
for antenna in range(self.num_antennas):
|
||||
for subcarrier in range(self.num_subcarriers):
|
||||
# Base amplitude varies with antenna and subcarrier
|
||||
amplitude = (
|
||||
self._amplitude_base
|
||||
* (1 + 0.2 * np.sin(2 * np.pi * subcarrier / self.num_subcarriers))
|
||||
* (1 + 0.1 * antenna)
|
||||
)
|
||||
|
||||
# Phase with spatial and frequency variation
|
||||
phase_offset = (
|
||||
self._phase
|
||||
+ 2 * np.pi * subcarrier / self.num_subcarriers
|
||||
+ np.pi * antenna / self.num_antennas
|
||||
)
|
||||
|
||||
# Simulated human movement
|
||||
movement = self.movement_amplitude * np.sin(
|
||||
2 * np.pi * self.movement_freq * time_axis
|
||||
)
|
||||
|
||||
signal_amplitude = amplitude * (1 + movement)
|
||||
signal_phase = phase_offset + movement * 0.5
|
||||
|
||||
# Additive complex Gaussian noise
|
||||
noise = np.random.normal(0, self.noise_level, self.num_samples) + 1j * np.random.normal(
|
||||
0, self.noise_level, self.num_samples
|
||||
)
|
||||
|
||||
csi_data[antenna, subcarrier, :] = (
|
||||
signal_amplitude * np.exp(1j * signal_phase) + noise
|
||||
)
|
||||
|
||||
return csi_data
|
||||
|
||||
def configure(self, config: Dict[str, Any]) -> None:
|
||||
"""Update generator parameters.
|
||||
|
||||
Args:
|
||||
config: Dictionary with optional keys:
|
||||
- sampling_rate: Adjusts internal frequency
|
||||
- noise_level: Sets noise standard deviation
|
||||
- num_subcarriers: Updates subcarrier count
|
||||
- num_antennas: Updates antenna count
|
||||
- movement_freq: Updates simulated movement frequency
|
||||
- movement_amplitude: Updates movement amplitude
|
||||
"""
|
||||
if "sampling_rate" in config:
|
||||
self._frequency = config["sampling_rate"] / 1000.0
|
||||
if "noise_level" in config:
|
||||
self.noise_level = config["noise_level"]
|
||||
if "num_subcarriers" in config:
|
||||
self.num_subcarriers = config["num_subcarriers"]
|
||||
if "num_antennas" in config:
|
||||
self.num_antennas = config["num_antennas"]
|
||||
if "movement_freq" in config:
|
||||
self.movement_freq = config["movement_freq"]
|
||||
if "movement_amplitude" in config:
|
||||
self.movement_amplitude = config["movement_amplitude"]
|
||||
|
||||
def get_router_info(self) -> Dict[str, Any]:
|
||||
"""Return mock router hardware information.
|
||||
|
||||
Returns:
|
||||
Dictionary mimicking router hardware info for testing.
|
||||
"""
|
||||
return {
|
||||
"model": "Mock Router",
|
||||
"firmware": "1.0.0-mock",
|
||||
"wifi_standard": "802.11ac",
|
||||
"antennas": self.num_antennas,
|
||||
"supported_bands": ["2.4GHz", "5GHz"],
|
||||
"csi_capabilities": {
|
||||
"max_subcarriers": self.num_subcarriers,
|
||||
"max_antennas": self.num_antennas,
|
||||
"sampling_rate": 1000,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
Mock pose data generator for testing and development.
|
||||
|
||||
This module provides synthetic pose estimation data for use in development
|
||||
and testing environments ONLY. The generated data mimics realistic human
|
||||
pose detection outputs including keypoints, bounding boxes, and activities.
|
||||
|
||||
WARNING: This module uses random number generation intentionally for test data.
|
||||
Do NOT use this module in production data paths.
|
||||
"""
|
||||
|
||||
import random
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Banner displayed when mock pose mode is active
|
||||
MOCK_POSE_BANNER = """
|
||||
================================================================================
|
||||
WARNING: MOCK POSE MODE ACTIVE - Using synthetic pose data
|
||||
|
||||
All pose detections are randomly generated and do NOT represent real humans.
|
||||
For real pose estimation, provide trained model weights and real CSI data.
|
||||
See docs/hardware-setup.md for configuration instructions.
|
||||
================================================================================
|
||||
"""
|
||||
|
||||
_banner_shown = False
|
||||
|
||||
|
||||
def _show_banner() -> None:
|
||||
"""Display the mock pose mode warning banner (once per session)."""
|
||||
global _banner_shown
|
||||
if not _banner_shown:
|
||||
logger.warning(MOCK_POSE_BANNER)
|
||||
_banner_shown = True
|
||||
|
||||
|
||||
def generate_mock_keypoints() -> List[Dict[str, Any]]:
|
||||
"""Generate mock keypoints for a single person.
|
||||
|
||||
Returns:
|
||||
List of 17 COCO-format keypoint dictionaries with name, x, y, confidence.
|
||||
"""
|
||||
keypoint_names = [
|
||||
"nose", "left_eye", "right_eye", "left_ear", "right_ear",
|
||||
"left_shoulder", "right_shoulder", "left_elbow", "right_elbow",
|
||||
"left_wrist", "right_wrist", "left_hip", "right_hip",
|
||||
"left_knee", "right_knee", "left_ankle", "right_ankle",
|
||||
]
|
||||
|
||||
keypoints = []
|
||||
for name in keypoint_names:
|
||||
keypoints.append({
|
||||
"name": name,
|
||||
"x": random.uniform(0.1, 0.9),
|
||||
"y": random.uniform(0.1, 0.9),
|
||||
"confidence": random.uniform(0.5, 0.95),
|
||||
})
|
||||
|
||||
return keypoints
|
||||
|
||||
|
||||
def generate_mock_bounding_box() -> Dict[str, float]:
|
||||
"""Generate a mock bounding box for a single person.
|
||||
|
||||
Returns:
|
||||
Dictionary with x, y, width, height as normalized coordinates.
|
||||
"""
|
||||
x = random.uniform(0.1, 0.6)
|
||||
y = random.uniform(0.1, 0.6)
|
||||
width = random.uniform(0.2, 0.4)
|
||||
height = random.uniform(0.3, 0.5)
|
||||
|
||||
return {"x": x, "y": y, "width": width, "height": height}
|
||||
|
||||
|
||||
def generate_mock_poses(max_persons: int = 3) -> List[Dict[str, Any]]:
|
||||
"""Generate mock pose detections for testing.
|
||||
|
||||
Args:
|
||||
max_persons: Maximum number of persons to generate (1 to max_persons).
|
||||
|
||||
Returns:
|
||||
List of pose detection dictionaries.
|
||||
"""
|
||||
_show_banner()
|
||||
|
||||
num_persons = random.randint(1, min(3, max_persons))
|
||||
poses = []
|
||||
|
||||
for i in range(num_persons):
|
||||
confidence = random.uniform(0.3, 0.95)
|
||||
|
||||
pose = {
|
||||
"person_id": i,
|
||||
"confidence": confidence,
|
||||
"keypoints": generate_mock_keypoints(),
|
||||
"bounding_box": generate_mock_bounding_box(),
|
||||
"activity": random.choice(["standing", "sitting", "walking", "lying"]),
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
poses.append(pose)
|
||||
|
||||
return poses
|
||||
|
||||
|
||||
def generate_mock_zone_occupancy(zone_id: str) -> Dict[str, Any]:
|
||||
"""Generate mock zone occupancy data.
|
||||
|
||||
Args:
|
||||
zone_id: Zone identifier.
|
||||
|
||||
Returns:
|
||||
Dictionary with occupancy count and person details.
|
||||
"""
|
||||
_show_banner()
|
||||
|
||||
count = random.randint(0, 5)
|
||||
persons = []
|
||||
|
||||
for i in range(count):
|
||||
persons.append({
|
||||
"person_id": f"person_{i}",
|
||||
"confidence": random.uniform(0.7, 0.95),
|
||||
"activity": random.choice(["standing", "sitting", "walking"]),
|
||||
})
|
||||
|
||||
return {
|
||||
"count": count,
|
||||
"max_occupancy": 10,
|
||||
"persons": persons,
|
||||
"timestamp": datetime.now(),
|
||||
}
|
||||
|
||||
|
||||
def generate_mock_zones_summary(
|
||||
zone_ids: Optional[List[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate mock zones summary data.
|
||||
|
||||
Args:
|
||||
zone_ids: List of zone identifiers. Defaults to zone_1 through zone_4.
|
||||
|
||||
Returns:
|
||||
Dictionary with per-zone occupancy and aggregate counts.
|
||||
"""
|
||||
_show_banner()
|
||||
|
||||
zones = zone_ids or ["zone_1", "zone_2", "zone_3", "zone_4"]
|
||||
zone_data = {}
|
||||
total_persons = 0
|
||||
active_zones = 0
|
||||
|
||||
for zone_id in zones:
|
||||
count = random.randint(0, 3)
|
||||
zone_data[zone_id] = {
|
||||
"occupancy": count,
|
||||
"max_occupancy": 10,
|
||||
"status": "active" if count > 0 else "inactive",
|
||||
}
|
||||
total_persons += count
|
||||
if count > 0:
|
||||
active_zones += 1
|
||||
|
||||
return {
|
||||
"total_persons": total_persons,
|
||||
"zones": zone_data,
|
||||
"active_zones": active_zones,
|
||||
}
|
||||
|
||||
|
||||
def generate_mock_historical_data(
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
zone_ids: Optional[List[str]] = None,
|
||||
aggregation_interval: int = 300,
|
||||
include_raw_data: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate mock historical pose data.
|
||||
|
||||
Args:
|
||||
start_time: Start of the time range.
|
||||
end_time: End of the time range.
|
||||
zone_ids: Zones to include. Defaults to zone_1, zone_2, zone_3.
|
||||
aggregation_interval: Seconds between data points.
|
||||
include_raw_data: Whether to include simulated raw detections.
|
||||
|
||||
Returns:
|
||||
Dictionary with aggregated_data, optional raw_data, and total_records.
|
||||
"""
|
||||
_show_banner()
|
||||
|
||||
zones = zone_ids or ["zone_1", "zone_2", "zone_3"]
|
||||
current_time = start_time
|
||||
aggregated_data = []
|
||||
raw_data = [] if include_raw_data else None
|
||||
|
||||
while current_time < end_time:
|
||||
data_point = {
|
||||
"timestamp": current_time,
|
||||
"total_persons": random.randint(0, 8),
|
||||
"zones": {},
|
||||
}
|
||||
|
||||
for zone_id in zones:
|
||||
data_point["zones"][zone_id] = {
|
||||
"occupancy": random.randint(0, 3),
|
||||
"avg_confidence": random.uniform(0.7, 0.95),
|
||||
}
|
||||
|
||||
aggregated_data.append(data_point)
|
||||
|
||||
if include_raw_data:
|
||||
for _ in range(random.randint(0, 5)):
|
||||
raw_data.append({
|
||||
"timestamp": current_time + timedelta(seconds=random.randint(0, aggregation_interval)),
|
||||
"person_id": f"person_{random.randint(1, 10)}",
|
||||
"zone_id": random.choice(zones),
|
||||
"confidence": random.uniform(0.5, 0.95),
|
||||
"activity": random.choice(["standing", "sitting", "walking"]),
|
||||
})
|
||||
|
||||
current_time += timedelta(seconds=aggregation_interval)
|
||||
|
||||
return {
|
||||
"aggregated_data": aggregated_data,
|
||||
"raw_data": raw_data,
|
||||
"total_records": len(aggregated_data),
|
||||
}
|
||||
|
||||
|
||||
def generate_mock_recent_activities(
|
||||
zone_id: Optional[str] = None,
|
||||
limit: int = 10,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Generate mock recent activity data.
|
||||
|
||||
Args:
|
||||
zone_id: Optional zone filter. If None, random zones are used.
|
||||
limit: Number of activities to generate.
|
||||
|
||||
Returns:
|
||||
List of activity dictionaries.
|
||||
"""
|
||||
_show_banner()
|
||||
|
||||
activities = []
|
||||
|
||||
for i in range(limit):
|
||||
activity = {
|
||||
"activity_id": f"activity_{i}",
|
||||
"person_id": f"person_{random.randint(1, 5)}",
|
||||
"zone_id": zone_id or random.choice(["zone_1", "zone_2", "zone_3"]),
|
||||
"activity": random.choice(["standing", "sitting", "walking", "lying"]),
|
||||
"confidence": random.uniform(0.6, 0.95),
|
||||
"timestamp": datetime.now() - timedelta(minutes=random.randint(0, 60)),
|
||||
"duration_seconds": random.randint(10, 300),
|
||||
}
|
||||
activities.append(activity)
|
||||
|
||||
return activities
|
||||
|
||||
|
||||
def generate_mock_statistics(
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate mock pose estimation statistics.
|
||||
|
||||
Args:
|
||||
start_time: Start of the statistics period.
|
||||
end_time: End of the statistics period.
|
||||
|
||||
Returns:
|
||||
Dictionary with detection counts, rates, and distributions.
|
||||
"""
|
||||
_show_banner()
|
||||
|
||||
total_detections = random.randint(100, 1000)
|
||||
successful_detections = int(total_detections * random.uniform(0.8, 0.95))
|
||||
|
||||
return {
|
||||
"total_detections": total_detections,
|
||||
"successful_detections": successful_detections,
|
||||
"failed_detections": total_detections - successful_detections,
|
||||
"success_rate": successful_detections / total_detections,
|
||||
"average_confidence": random.uniform(0.75, 0.90),
|
||||
"average_processing_time_ms": random.uniform(50, 200),
|
||||
"unique_persons": random.randint(5, 20),
|
||||
"most_active_zone": random.choice(["zone_1", "zone_2", "zone_3"]),
|
||||
"activity_distribution": {
|
||||
"standing": random.uniform(0.3, 0.5),
|
||||
"sitting": random.uniform(0.2, 0.4),
|
||||
"walking": random.uniform(0.1, 0.3),
|
||||
"lying": random.uniform(0.0, 0.1),
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user