make variables public, vesion variable and minor changes
This commit is contained in:
+12
-3
@@ -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'
|
||||
]
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user