refactor: prune orphaned attic modules and standardize project configurations
- Move orphaned modules (data_segment.py, contour_models.py, marching_squares.py) to attic/. - Standardize local settings logic, serial port parameters, and graph view plots. - Update pyproject.toml pyside6-project dependencies and configure gitignore.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
|
||||
# ===== Single Contour Segment =====
|
||||
@dataclass
|
||||
class ContourSegment:
|
||||
start_x: float
|
||||
start_y: float
|
||||
end_x: float
|
||||
end_y: float
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return (self.start_x, self.start_y)
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
return (self.end_x, self.end_y)
|
||||
|
||||
|
||||
# ===== Contour Line =====
|
||||
@dataclass
|
||||
class ContourLine:
|
||||
level: float
|
||||
segments: List[ContourSegment] = field(default_factory=list)
|
||||
@@ -0,0 +1,55 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
# ===== Time-Indexed Data Segment =====
|
||||
class DataSegment:
|
||||
# ===== Lifecycle =====
|
||||
def __init__(
|
||||
self,
|
||||
full_data: list[float],
|
||||
start_time: datetime,
|
||||
start_index: int,
|
||||
end_index: int,
|
||||
):
|
||||
if not isinstance(start_index, int):
|
||||
raise TypeError("start_index must be an integer")
|
||||
if not isinstance(end_index, int):
|
||||
raise TypeError("end_index must be an integer")
|
||||
"""
|
||||
full_data: The complete list of sensor readings.
|
||||
start_time: The timestamp of the very first reading in full_data.
|
||||
start_index: The offset (in seconds) from start_time to the beginning of this segment.
|
||||
end_index: The offset (in seconds) from start_time to the end of this segment.
|
||||
"""
|
||||
self.full_data = full_data
|
||||
self._base_start_time = start_time
|
||||
self._start_index = start_index
|
||||
self._end_index = end_index
|
||||
self.chamber = ""
|
||||
self.notes = ""
|
||||
|
||||
# ===== Index Properties =====
|
||||
@property
|
||||
def start_index(self) -> int:
|
||||
return self._start_index
|
||||
|
||||
@start_index.setter
|
||||
def start_index(self, value: int):
|
||||
self._start_index = value
|
||||
|
||||
@property
|
||||
def end_index(self) -> int:
|
||||
return self._end_index
|
||||
|
||||
@end_index.setter
|
||||
def end_index(self, value: int):
|
||||
self._end_index = value
|
||||
|
||||
# ===== Derived Time Properties =====
|
||||
@property
|
||||
def start_time(self) -> datetime:
|
||||
return self._base_start_time + timedelta(seconds=self.start_index)
|
||||
|
||||
@property
|
||||
def end_time(self) -> datetime:
|
||||
return self._base_start_time + timedelta(seconds=self.end_index)
|
||||
@@ -0,0 +1,139 @@
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pygui.backend.attic.contour_models import ContourLine, ContourSegment
|
||||
|
||||
|
||||
# ===== Contour Generation =====
|
||||
class MarchingSquares:
|
||||
|
||||
# ===== Public API =====
|
||||
@staticmethod
|
||||
def generate_contours(grid: np.ndarray, levels: List[float]) -> List[ContourLine]:
|
||||
"""
|
||||
Generate contour lines for a 2D grid at specified levels.
|
||||
|
||||
Args:
|
||||
grid: 2D numpy array (shape: [width, height])
|
||||
levels: List of contour levels to compute
|
||||
|
||||
Returns:
|
||||
List of ContourLine objects
|
||||
"""
|
||||
if grid.size == 0:
|
||||
return []
|
||||
|
||||
width, height = grid.shape[0], grid.shape[1]
|
||||
contours = []
|
||||
|
||||
for level in levels:
|
||||
contour = ContourLine(level=level)
|
||||
|
||||
# Iterate over each cell (x, y) in the grid
|
||||
for y in range(height - 1):
|
||||
for x in range(width - 1):
|
||||
v0 = float(grid[x, y]) # top-left
|
||||
v1 = float(grid[x + 1, y]) # top-right
|
||||
v2 = float(grid[x + 1, y + 1]) # bottom-right
|
||||
v3 = float(grid[x, y + 1]) # bottom-left
|
||||
|
||||
if any(np.isnan([v0, v1, v2, v3])):
|
||||
continue # Skip cells with NaN values
|
||||
|
||||
state = (
|
||||
(1 if v0 > level else 0)
|
||||
| (2 if v1 > level else 0)
|
||||
| (4 if v2 > level else 0)
|
||||
| (8 if v3 > level else 0)
|
||||
)
|
||||
|
||||
seg = MarchingSquares._get_segment(
|
||||
x, y, v0, v1, v2, v3, level, state
|
||||
)
|
||||
if seg is not None:
|
||||
contour.segments.append(seg)
|
||||
|
||||
contours.append(contour)
|
||||
|
||||
return contours
|
||||
|
||||
# ===== Geometry Helpers =====
|
||||
@staticmethod
|
||||
def _lerp(
|
||||
x1: float,
|
||||
y1: float,
|
||||
x2: float,
|
||||
y2: float,
|
||||
val1: float,
|
||||
val2: float,
|
||||
level: float,
|
||||
) -> Tuple[float, float]:
|
||||
"""Linear interpolation between (x1,y1) and (x2,y2)."""
|
||||
if val2 == val1:
|
||||
return (x1 + x2) / 2, (y1 + y2) / 2
|
||||
t = (level - val1) / (val2 - val1)
|
||||
px = x1 + t * (x2 - x1)
|
||||
py = y1 + t * (y2 - y1)
|
||||
return px, py
|
||||
|
||||
# ===== Segment Lookup =====
|
||||
@staticmethod
|
||||
def _get_segment(
|
||||
x: int,
|
||||
y: int,
|
||||
v0: float,
|
||||
v1: float,
|
||||
v2: float,
|
||||
v3: float,
|
||||
level: float,
|
||||
state: int,
|
||||
) -> Optional[ContourSegment]:
|
||||
"""Return a ContourSegment for the given cell and state."""
|
||||
if state in (10,): # Ambiguous case — skip
|
||||
return None
|
||||
|
||||
# Map C# states to Python logic
|
||||
if state == 1 or state == 14:
|
||||
start = MarchingSquares._lerp(x, y, x + 1, y, v0, v1, level)
|
||||
end = MarchingSquares._lerp(x, y, x, y + 1, v0, v3, level)
|
||||
elif state == 2 or state == 13:
|
||||
start = MarchingSquares._lerp(x + 1, y, x + 1, y + 1, v1, v2, level)
|
||||
end = MarchingSquares._lerp(x, y, x + 1, y, v0, v1, level)
|
||||
elif state == 3 or state == 12:
|
||||
start = MarchingSquares._lerp(x, y, x, y + 1, v0, v3, level)
|
||||
end = MarchingSquares._lerp(x + 1, y, x + 1, y + 1, v1, v2, level)
|
||||
elif state == 4 or state == 11:
|
||||
start = MarchingSquares._lerp(x + 1, y, x + 1, y + 1, v1, v2, level)
|
||||
end = MarchingSquares._lerp(x + 1, y + 1, x, y + 1, v2, v3, level)
|
||||
elif state == 5:
|
||||
start = MarchingSquares._lerp(x, y, x, y + 1, v0, v3, level)
|
||||
end = MarchingSquares._lerp(x + 1, y, x + 1, y + 1, v1, v2, level)
|
||||
elif state == 6 or state == 9:
|
||||
start = MarchingSquares._lerp(x, y, x + 1, y, v0, v1, level)
|
||||
end = MarchingSquares._lerp(x + 1, y + 1, x, y + 1, v2, v3, level)
|
||||
elif state == 7 or state == 8:
|
||||
start = MarchingSquares._lerp(x, y, x, y + 1, v0, v3, level)
|
||||
end = MarchingSquares._lerp(x + 1, y + 1, x, y + 1, v2, v3, level)
|
||||
else:
|
||||
return None
|
||||
|
||||
return ContourSegment(
|
||||
start_x=start[0], start_y=start[1], end_x=end[0], end_y=end[1]
|
||||
)
|
||||
|
||||
# ===== Color Mapping =====
|
||||
@staticmethod
|
||||
def color_from_level(
|
||||
value: float, min_val: float, max_val: float
|
||||
) -> Tuple[int, int, int]:
|
||||
"""Return (R, G, B) tuple for a value between min and max."""
|
||||
range_val = max_val - min_val
|
||||
if range_val == 0:
|
||||
t = 0.5
|
||||
else:
|
||||
t = max(0.0, min(1.0, (value - min_val) / range_val))
|
||||
|
||||
r = int(255 * t)
|
||||
b = int(255 * (1 - t))
|
||||
return (r, 0, b)
|
||||
Reference in New Issue
Block a user