make variables public, vesion variable and minor changes

This commit is contained in:
bluepulsar
2026-01-04 13:29:26 +08:00
parent 807785ca7f
commit 1fe6fed9e6
6 changed files with 127 additions and 112 deletions
+12 -3
View File
@@ -1,5 +1,5 @@
"""
Orisys - Optical Tactile Sensor SDK
orisys - Optical Tactile Sensor SDK
A Python library for processing and analyzing optical tactile sensor data,
including image stitching, optical flow tracking, and deformation decomposition.
@@ -9,7 +9,6 @@ __version__ = "0.2.0"
__author__ = "Orisys"
from .sensor import Sensor
from .MultiSensor import MultiSensor
from . import util
from . import configs
@@ -17,4 +16,14 @@ from . import configs
list_config = configs.list_config
export_config = configs.export_config
__all__ = ['Sensor', 'MultiSensor', 'util', 'list_config', 'export_config']
__all__ = [
# class
'Sensor',
# util functions
'util',
# configs utilities functions
'list_config',
'export_config'
]
+24 -25
View File
@@ -67,6 +67,29 @@ def list_config():
config_files = sorted(CONFIG_DIR.glob("*.json"))
return [f.stem for f in config_files]
def export_config(config_name = None, path="./config/exported_config.json"):
"""
Export configuration to JSON file with readable formatting.
Arrays/lists are kept on a single line, while nested objects are indented.
Args:
config: Configuration dictionary to export
path: Output file path (default: "./config/exported_config.json")
Returns:
The exported config dictionary
"""
config = load_config(config_name)
Path(path).parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
formatted = _format_json_value(config)
f.write(formatted)
f.write('\n') # Add trailing newline
print(f"Config name: {config_name} exported to {path}")
return config
def _format_json_value(value, indent_level=0, indent_str=" "):
"""
Recursively format JSON value with arrays/lists on single line.
@@ -96,28 +119,4 @@ def _format_json_value(value, indent_level=0, indent_str=" "):
return "[" + ", ".join(items) + "]"
else:
# Primitive types (string, number, bool, None)
return json.dumps(value, ensure_ascii=False)
def export_config(config_name = None, path="./config/exported_config.json"):
"""
Export configuration to JSON file with readable formatting.
Arrays/lists are kept on a single line, while nested objects are indented.
Args:
config: Configuration dictionary to export
path: Output file path (default: "./config/exported_config.json")
Returns:
The exported config dictionary
"""
config = load_config(config_name)
Path(path).parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
formatted = _format_json_value(config)
f.write(formatted)
f.write('\n') # Add trailing newline
print(f"Config name: {config_name} exported to {path}")
return config
return json.dumps(value, ensure_ascii=False)
+58 -58
View File
@@ -92,21 +92,21 @@ class Sensor:
self.compute_deformation_valid = False
self.compute_contact_valid = False
# Data Variables
self.__img = None
self.__img_size_raw = None
self.__img_filtered = None
self.__flow = None
self.__vnormal = None
self.__vshear = None
self.__n_boxes = None
self.__fnormal = self.__fshearx = self.__fsheary = self.__ftor = 0.0
# Data Variables (public)
self.img = None
self.img_size_raw = None
self.img_filtered = None
self.flow = None
self.vnormal = None
self.vshear = None
self.n_boxes = None
self.fnormal = self.fshearx = self.fsheary = self.ftor = 0.0
# Contact Data Variables
self.__centroid = None
self.__centroid_found = False
self.__contour = None
self.__depth_map = None
# Contact Data Variables (public)
self.centroid = None
self.centroid_found = False
self.contour = None
self.depth_map = None
# Timing
self.last_log_time = time.perf_counter()
@@ -180,26 +180,26 @@ class Sensor:
img = np.ascontiguousarray(self.__stitcher.stitch_rt_optimized())
if log_timing: timings['stitch'] = (time.perf_counter() - t0) * 1000
self.__img_size_raw = img.shape
self.img_size_raw = img.shape
# Resize
img = cv2.resize(img, tuple(self._config_resolution_stitch), interpolation=cv2.INTER_CUBIC)
# Temporal filtering (noise reduction)
alpha = 0.7
if self.__img_filtered is None:
self.__img_filtered = img.astype(np.float32)
if self.img_filtered is None:
self.img_filtered = img.astype(np.float32)
else:
self.__img_filtered = alpha * img + (1 - alpha) * self.__img_filtered
self.img_filtered = alpha * img + (1 - alpha) * self.img_filtered
self.__img = self.__img_filtered.astype(np.uint8)
self.img = self.img_filtered.astype(np.uint8)
if log_timing:
timings['total'] = (time.perf_counter() - t_start) * 1000
self._print_timings("get_img", timings)
if self.mirror:
self.__img = cv2.flip(self.__img, 1)
self.img = cv2.flip(self.img, 1)
return self.__img.copy()
return self.img.copy()
def compute_deformation(self):
"""
@@ -208,7 +208,7 @@ class Sensor:
Returns:
np.ndarray: Optical flow field
"""
if self.__img is None:
if self.img is None:
raise Exception("Warning: Get image first")
self.__compute_flow()
@@ -220,17 +220,17 @@ class Sensor:
return True
def __compute_flow(self):
if self.__img is None:
if self.img is None:
raise Exception("Warning: Get image first")
flow = self.__tracker.track(self.__img)
flow = self.__tracker.track(self.img)
if hasattr(flow, 'download'):
flow = flow.download()
self.__flow = flow
self.flow = flow
def __compute_hhd(self):
flow = self.__flow
flow = self.flow
if flow is None:
raise Exception("Warning: Compute flow first")
@@ -243,11 +243,11 @@ class Sensor:
_, forces = self.__hhd.get_force_gpu_v2(flow)
else:
_, forces = self.__hhd.get_force(flow)
self.__fnormal, self.__fshearx, self.__fsheary, self.__ftor = forces
self.fnormal, self.fshearx, self.fsheary, self.ftor = forces
# Get components
self.__vnormal = self.__hhd.get_normal()
self.__vshear = self.__hhd.get_shear()
self.vnormal = self.__hhd.get_normal()
self.vshear = self.__hhd.get_shear()
# Update state
self.compute_deformation_valid = True
@@ -259,7 +259,7 @@ class Sensor:
Compute contact information from vnormal field.
Args:
vnormal: Optional vnormal field. If None, uses self.__vnormal.
vnormal: Optional vnormal field. If None, uses self.vnormal.
Returns:
dict: Contact result with centroid, contour, and depth_map
@@ -267,7 +267,7 @@ class Sensor:
multiple_foe = False
# Use provided vnormal or fall back to internal state
vnormal_to_use = vnormal if vnormal is not None else self.__vnormal
vnormal_to_use = vnormal if vnormal is not None else self.vnormal
if vnormal_to_use is None:
raise Exception("Warning: vnormal is required for contact computation")
@@ -280,11 +280,11 @@ class Sensor:
else:
result = self.__contact3d.compute_foe(vnormal_to_use)
self.__centroid = result['centroid']
self.__centroid_found = result['centroid_found']
self.centroid = result['centroid']
self.centroid_found = result['centroid_found']
if 'contour' in result:
self.__contour = result['contour']
self.__depth_map = result['divergence_map']
self.contour = result['contour']
self.depth_map = result['divergence_map']
self.compute_contact_valid = True
return result
@@ -301,7 +301,7 @@ class Sensor:
Tuple: Requested data in order
"""
if any(arg in [self.info.IMG] for arg in args):
if self.__img is None:
if self.img is None:
raise Exception("Warning: Get image first")
if any(arg in [self.info.VRAW, self.info.VNORMAL, self.info.VSHEAR, self.info.FNORMAL, self.info.FSHEARX, self.info.FSHEARY] for arg in args):
@@ -314,26 +314,26 @@ class Sensor:
mapping = {
# Return copies of arrays to prevent external modification of internal state
self.info.VRAW: lambda: self.__flow.copy() if self.__flow is not None else np.array([]),
self.info.VNORMAL: lambda: self.__vnormal.copy() if self.__vnormal is not None else np.array([]),
self.info.VSHEAR: lambda: self.__vshear.copy() if self.__vshear is not None else np.array([]),
self.info.VRAW: lambda: self.flow.copy() if self.flow is not None else np.array([]),
self.info.VNORMAL: lambda: self.vnormal.copy() if self.vnormal is not None else np.array([]),
self.info.VSHEAR: lambda: self.vshear.copy() if self.vshear is not None else np.array([]),
# Scalar values are safe to return directly
self.info.FNORMAL: lambda: self.__fnormal,
self.info.FSHEARX: lambda: self.__fshearx,
self.info.FSHEARY: lambda: self.__fsheary,
self.info.FNORMAL: lambda: self.fnormal,
self.info.FSHEARX: lambda: self.fshearx,
self.info.FSHEARY: lambda: self.fsheary,
self.info.FPS: lambda: self._fps,
self.info.TIMESTAMP: lambda: time.strftime('%H:%M:%S'),
# Return copies of arrays to prevent external modification
self.info.DEPTH: lambda: self.__depth_map.copy() if self.__depth_map is not None else np.array([]),
self.info.DEPTH: lambda: self.depth_map.copy() if self.depth_map is not None else np.array([]),
# Centroid: can be tuple, list, or array - return a copy to prevent modification
# Return None if centroid is not found or empty
self.info.CENTROID: lambda: (
np.array(self.__centroid).copy()
if self.__centroid is not None and len(self.__centroid) > 0
np.array(self.centroid).copy()
if self.centroid is not None and len(self.centroid) > 0
else None
),
self.info.CONTOUR: lambda: self.__contour.copy() if self.__contour is not None else np.array([]),
self.info.IMG: lambda: self.__img.copy() if self.__img is not None else np.array([]),
self.info.CONTOUR: lambda: self.contour.copy() if self.contour is not None else np.array([]),
self.info.IMG: lambda: self.img.copy() if self.img is not None else np.array([]),
}
return tuple(mapping.get(arg, lambda: None)() for arg in args if isinstance(arg, self.info))
@@ -475,7 +475,7 @@ class Sensor:
break
v_mean0 = v_mean
self.__n_boxes = n_boxes
self.n_boxes = n_boxes
self.stitch_valid = self.isstitch and n_boxes == self._config_units
status = "valid for stitching" if self.stitch_valid else "non-stitch mode"
@@ -607,9 +607,9 @@ class Sensor:
# continue
# # Put the processed image in the queue
# # get_img() updates self.__img, so we use that
# if self.__img is not None:
# self._queue_put(self._capture_queue, self.__img.copy())
# # get_img() updates self.img, so we use that
# if self.img is not None:
# self._queue_put(self._capture_queue, self.img.copy())
# except Exception as e:
# if self.verbose: print(f"Capture error: {e}")
@@ -619,9 +619,9 @@ class Sensor:
# try:
# img = self._capture_queue.get(timeout=0.1)
# # Set __img to reuse __compute_flow()
# self.__img = img
# self.img = img
# self.__compute_flow()
# flow = self.__flow.copy() if self.__flow is not None else None
# flow = self.flow.copy() if self.flow is not None else None
# self._queue_put(self._flow_queue, (img, flow))
# except Empty:
# continue
@@ -635,16 +635,16 @@ class Sensor:
# img, flow = self._flow_queue.get(timeout=0.1)
# # Set __flow to reuse __compute_hhd()
# self.__flow = flow
# self.flow = flow
# self.__compute_hhd()
# # Get results from internal variables set by __compute_hhd()
# result = {
# 'img': img, 'flow': flow,
# 'normal': self.__vnormal.copy() if self.__vnormal is not None else None,
# 'shear': self.__vshear.copy() if self.__vshear is not None else None,
# 'normal': self.vnormal.copy() if self.vnormal is not None else None,
# 'shear': self.vshear.copy() if self.vshear is not None else None,
# # 'depth': getFilteredDensity(flow, use_cuda=self.cuda),
# 'fnormal': self.__fnormal, 'fshearx': self.__fshearx,
# 'fsheary': self.__fsheary, 'ftor': self.__ftor,
# 'fnormal': self.fnormal, 'fshearx': self.fshearx,
# 'fsheary': self.fsheary, 'ftor': self.ftor,
# }
# # Always include contact fields to ensure consistent result structure
+1
View File
@@ -103,3 +103,4 @@ def draw_contact(image, contour, centroid, point_color=(0, 255, 0), region_color
cv2.circle(image, (cx, cy), 10, point_color, 2)
return image