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
+25 -22
View File
@@ -5,12 +5,12 @@
1. 根据平台和python 版本下载对应的`.whl`文件 1. 根据平台和python 版本下载对应的`.whl`文件
2. 运行pip命令安装: 2. 运行pip命令安装:
```bash ```bash
pip install orisys-{version}-{python}-none-{os}_amd64.whl pip install orisys-{version}-{python-version}-none-{os}_amd64.whl
``` ```
3. 并测试导入: 3. 并测试导入:
```python ```python
import orisys import orisys
orisys.__version__ print(orisys.__version__)
``` ```
--- ---
## `Sensor` 类别 ## `Sensor` 类别
@@ -39,7 +39,7 @@ sensor = Sensor("/data/data.mp4")
--- ---
## 2. `get_img` 方法 ## 2. `Sensor.get_img` 方法
### 描述 ### 描述
@@ -59,7 +59,7 @@ if img is None:
``` ```
--- ---
## 3. `compute_deformation` 方法 ## 3. `Sensor.compute_deformation` 方法
### 描述 ### 描述
@@ -81,7 +81,7 @@ if img is None:
sensor.compute_deformation() sensor.compute_deformation()
``` ```
--- ---
## 4. `compute_contact` 方法 ## 4. `Sensor.compute_contact` 方法
### 描述 ### 描述
基于已计算的法向形变场进行接触区域分析,输出接触质心与轮廓等信息,并生成接触强度图。 基于已计算的法向形变场进行接触区域分析,输出接触质心与轮廓等信息,并生成接触强度图。
@@ -120,7 +120,7 @@ centroid, contour, depth = sensor.read_info(
) )
``` ```
--- ---
## 4. `read_info` 方法 ## 4. `Sensor.read_info` 方法
### 描述 ### 描述
@@ -176,7 +176,7 @@ flow = sensor.read_info(sensor.info.VRAW)
``` ```
--- ---
## 5. `disconnect` 方法 ## 5. `Sensor.disconnect` 方法
### 描述 ### 描述
@@ -188,21 +188,7 @@ flow = sensor.read_info(sensor.info.VRAW)
--- ---
# 配置标定相关 # 配置标定相关
## 1. `export_config` 方法 ## 1. `list_config` 方法
### 描述
导出当前传感器实例使用的配置参数字典。
### 输入参数
* **config_name**: 需要导出的配置参数字典名字`str`
* **path**: 导出的路径,默认`"./config/exported_config.json"`
### 返回
* `dict`: 导出的配置参数字典。
---
## 2. `list_config` 方法
### 描述 ### 描述
列出内置传感器配置参数字典。 列出内置传感器配置参数字典。
@@ -210,6 +196,23 @@ flow = sensor.read_info(sensor.info.VRAW)
### 返回 ### 返回
* `list`: 内置传感器配置参数字典的名字列表。 * `list`: 内置传感器配置参数字典的名字列表。
---
## 2. `export_config` 方法
### 描述
导出当前传感器实例使用的配置参数字典。
### 输入参数
* **config_name**: 需要导出的配置参数字典名字,`str`
* **path**: 导出的路径,默认`"./config/exported_config.json"`
### 返回
* `dict`: 导出的配置参数字典。
### 保存
* 导出内置的`config_name`配置参数字典至`path`
--- ---
--- ---
+7 -4
View File
@@ -11,6 +11,9 @@ import platform
from pathlib import Path from pathlib import Path
# Package version
VERSION = "0.2.0"
# Python versions to build for # Python versions to build for
PYTHON_VERSIONS = ['3.8', '3.9', '3.10'] PYTHON_VERSIONS = ['3.8', '3.9', '3.10']
@@ -157,7 +160,7 @@ def copy_additional_files():
shutil.copytree('config', 'dist_obfuscated/config', dirs_exist_ok=True) shutil.copytree('config', 'dist_obfuscated/config', dirs_exist_ok=True)
# Copy README and other metadata files # Copy README and other metadata files
files_to_copy = ['README.md', 'Readme(未完成).md', 'requirements.txt', 'LICENSE'] files_to_copy = ['README.md', 'requirements.txt', 'LICENSE']
for file in files_to_copy: for file in files_to_copy:
if os.path.exists(file): if os.path.exists(file):
print(f"Copying {file}...") print(f"Copying {file}...")
@@ -217,7 +220,7 @@ for pkg in packages:
setup( setup(
name="orisys", name="orisys",
version="0.2.0", version="{VERSION}",
description="Orisys - Optical Tactile Sensor SDK (Protected)", description="Orisys - Optical Tactile Sensor SDK (Protected)",
author="Your Name", author="Your Name",
package_dir={{"": "src"}}, package_dir={{"": "src"}},
@@ -335,7 +338,7 @@ def build_wheel_for_version(python_version):
return False return False
print(f"✅ Wheel for Python {python_version} on {PLATFORM_NAME} built successfully!") print(f"✅ Wheel for Python {python_version} on {PLATFORM_NAME} built successfully!")
print(f" Expected: orisys-0.2.0-{py_tag}-none-{plat_tag}.whl") print(f" Expected: orisys-{VERSION}-{py_tag}-none-{plat_tag}.whl")
return True return True
@@ -444,7 +447,7 @@ def main():
for wheel in Path(f'dist/{PLATFORM_NAME}').glob('*.whl'): for wheel in Path(f'dist/{PLATFORM_NAME}').glob('*.whl'):
print(f" - {wheel.name}") print(f" - {wheel.name}")
print("\nInstall with:") print("\nInstall with:")
print(f" pip install dist/{PLATFORM_NAME}/orisys-0.2.0-*.whl") print(f" pip install dist/{PLATFORM_NAME}/orisys-{VERSION}-*.whl")
if __name__ == '__main__': if __name__ == '__main__':
+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, A Python library for processing and analyzing optical tactile sensor data,
including image stitching, optical flow tracking, and deformation decomposition. including image stitching, optical flow tracking, and deformation decomposition.
@@ -9,7 +9,6 @@ __version__ = "0.2.0"
__author__ = "Orisys" __author__ = "Orisys"
from .sensor import Sensor from .sensor import Sensor
from .MultiSensor import MultiSensor
from . import util from . import util
from . import configs from . import configs
@@ -17,4 +16,14 @@ from . import configs
list_config = configs.list_config list_config = configs.list_config
export_config = configs.export_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'
]
+23 -24
View File
@@ -67,6 +67,29 @@ def list_config():
config_files = sorted(CONFIG_DIR.glob("*.json")) config_files = sorted(CONFIG_DIR.glob("*.json"))
return [f.stem for f in config_files] 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=" "): def _format_json_value(value, indent_level=0, indent_str=" "):
""" """
Recursively format JSON value with arrays/lists on single line. Recursively format JSON value with arrays/lists on single line.
@@ -97,27 +120,3 @@ def _format_json_value(value, indent_level=0, indent_str=" "):
else: else:
# Primitive types (string, number, bool, None) # Primitive types (string, number, bool, None)
return json.dumps(value, ensure_ascii=False) 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
+58 -58
View File
@@ -92,21 +92,21 @@ class Sensor:
self.compute_deformation_valid = False self.compute_deformation_valid = False
self.compute_contact_valid = False self.compute_contact_valid = False
# Data Variables # Data Variables (public)
self.__img = None self.img = None
self.__img_size_raw = None self.img_size_raw = None
self.__img_filtered = None self.img_filtered = None
self.__flow = None self.flow = None
self.__vnormal = None self.vnormal = None
self.__vshear = None self.vshear = None
self.__n_boxes = None self.n_boxes = None
self.__fnormal = self.__fshearx = self.__fsheary = self.__ftor = 0.0 self.fnormal = self.fshearx = self.fsheary = self.ftor = 0.0
# Contact Data Variables # Contact Data Variables (public)
self.__centroid = None self.centroid = None
self.__centroid_found = False self.centroid_found = False
self.__contour = None self.contour = None
self.__depth_map = None self.depth_map = None
# Timing # Timing
self.last_log_time = time.perf_counter() self.last_log_time = time.perf_counter()
@@ -180,26 +180,26 @@ class Sensor:
img = np.ascontiguousarray(self.__stitcher.stitch_rt_optimized()) img = np.ascontiguousarray(self.__stitcher.stitch_rt_optimized())
if log_timing: timings['stitch'] = (time.perf_counter() - t0) * 1000 if log_timing: timings['stitch'] = (time.perf_counter() - t0) * 1000
self.__img_size_raw = img.shape self.img_size_raw = img.shape
# Resize # Resize
img = cv2.resize(img, tuple(self._config_resolution_stitch), interpolation=cv2.INTER_CUBIC) img = cv2.resize(img, tuple(self._config_resolution_stitch), interpolation=cv2.INTER_CUBIC)
# Temporal filtering (noise reduction) # Temporal filtering (noise reduction)
alpha = 0.7 alpha = 0.7
if self.__img_filtered is None: if self.img_filtered is None:
self.__img_filtered = img.astype(np.float32) self.img_filtered = img.astype(np.float32)
else: 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: if log_timing:
timings['total'] = (time.perf_counter() - t_start) * 1000 timings['total'] = (time.perf_counter() - t_start) * 1000
self._print_timings("get_img", timings) self._print_timings("get_img", timings)
if self.mirror: 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): def compute_deformation(self):
""" """
@@ -208,7 +208,7 @@ class Sensor:
Returns: Returns:
np.ndarray: Optical flow field np.ndarray: Optical flow field
""" """
if self.__img is None: if self.img is None:
raise Exception("Warning: Get image first") raise Exception("Warning: Get image first")
self.__compute_flow() self.__compute_flow()
@@ -220,17 +220,17 @@ class Sensor:
return True return True
def __compute_flow(self): def __compute_flow(self):
if self.__img is None: if self.img is None:
raise Exception("Warning: Get image first") raise Exception("Warning: Get image first")
flow = self.__tracker.track(self.__img) flow = self.__tracker.track(self.img)
if hasattr(flow, 'download'): if hasattr(flow, 'download'):
flow = flow.download() flow = flow.download()
self.__flow = flow self.flow = flow
def __compute_hhd(self): def __compute_hhd(self):
flow = self.__flow flow = self.flow
if flow is None: if flow is None:
raise Exception("Warning: Compute flow first") raise Exception("Warning: Compute flow first")
@@ -243,11 +243,11 @@ class Sensor:
_, forces = self.__hhd.get_force_gpu_v2(flow) _, forces = self.__hhd.get_force_gpu_v2(flow)
else: else:
_, forces = self.__hhd.get_force(flow) _, 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 # Get components
self.__vnormal = self.__hhd.get_normal() self.vnormal = self.__hhd.get_normal()
self.__vshear = self.__hhd.get_shear() self.vshear = self.__hhd.get_shear()
# Update state # Update state
self.compute_deformation_valid = True self.compute_deformation_valid = True
@@ -259,7 +259,7 @@ class Sensor:
Compute contact information from vnormal field. Compute contact information from vnormal field.
Args: Args:
vnormal: Optional vnormal field. If None, uses self.__vnormal. vnormal: Optional vnormal field. If None, uses self.vnormal.
Returns: Returns:
dict: Contact result with centroid, contour, and depth_map dict: Contact result with centroid, contour, and depth_map
@@ -267,7 +267,7 @@ class Sensor:
multiple_foe = False multiple_foe = False
# Use provided vnormal or fall back to internal state # 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: if vnormal_to_use is None:
raise Exception("Warning: vnormal is required for contact computation") raise Exception("Warning: vnormal is required for contact computation")
@@ -280,11 +280,11 @@ class Sensor:
else: else:
result = self.__contact3d.compute_foe(vnormal_to_use) result = self.__contact3d.compute_foe(vnormal_to_use)
self.__centroid = result['centroid'] self.centroid = result['centroid']
self.__centroid_found = result['centroid_found'] self.centroid_found = result['centroid_found']
if 'contour' in result: if 'contour' in result:
self.__contour = result['contour'] self.contour = result['contour']
self.__depth_map = result['divergence_map'] self.depth_map = result['divergence_map']
self.compute_contact_valid = True self.compute_contact_valid = True
return result return result
@@ -301,7 +301,7 @@ class Sensor:
Tuple: Requested data in order Tuple: Requested data in order
""" """
if any(arg in [self.info.IMG] for arg in args): 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") 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): 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 = { mapping = {
# Return copies of arrays to prevent external modification of internal state # 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.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.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.VSHEAR: lambda: self.vshear.copy() if self.vshear is not None else np.array([]),
# Scalar values are safe to return directly # Scalar values are safe to return directly
self.info.FNORMAL: lambda: self.__fnormal, self.info.FNORMAL: lambda: self.fnormal,
self.info.FSHEARX: lambda: self.__fshearx, self.info.FSHEARX: lambda: self.fshearx,
self.info.FSHEARY: lambda: self.__fsheary, self.info.FSHEARY: lambda: self.fsheary,
self.info.FPS: lambda: self._fps, self.info.FPS: lambda: self._fps,
self.info.TIMESTAMP: lambda: time.strftime('%H:%M:%S'), self.info.TIMESTAMP: lambda: time.strftime('%H:%M:%S'),
# Return copies of arrays to prevent external modification # 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 # Centroid: can be tuple, list, or array - return a copy to prevent modification
# Return None if centroid is not found or empty # Return None if centroid is not found or empty
self.info.CENTROID: lambda: ( self.info.CENTROID: lambda: (
np.array(self.__centroid).copy() np.array(self.centroid).copy()
if self.__centroid is not None and len(self.__centroid) > 0 if self.centroid is not None and len(self.centroid) > 0
else None else None
), ),
self.info.CONTOUR: lambda: self.__contour.copy() if self.__contour 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([]), 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)) return tuple(mapping.get(arg, lambda: None)() for arg in args if isinstance(arg, self.info))
@@ -475,7 +475,7 @@ class Sensor:
break break
v_mean0 = v_mean 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 self.stitch_valid = self.isstitch and n_boxes == self._config_units
status = "valid for stitching" if self.stitch_valid else "non-stitch mode" status = "valid for stitching" if self.stitch_valid else "non-stitch mode"
@@ -607,9 +607,9 @@ class Sensor:
# continue # continue
# # Put the processed image in the queue # # Put the processed image in the queue
# # get_img() updates self.__img, so we use that # # get_img() updates self.img, so we use that
# if self.__img is not None: # if self.img is not None:
# self._queue_put(self._capture_queue, self.__img.copy()) # self._queue_put(self._capture_queue, self.img.copy())
# except Exception as e: # except Exception as e:
# if self.verbose: print(f"Capture error: {e}") # if self.verbose: print(f"Capture error: {e}")
@@ -619,9 +619,9 @@ class Sensor:
# try: # try:
# img = self._capture_queue.get(timeout=0.1) # img = self._capture_queue.get(timeout=0.1)
# # Set __img to reuse __compute_flow() # # Set __img to reuse __compute_flow()
# self.__img = img # self.img = img
# self.__compute_flow() # 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)) # self._queue_put(self._flow_queue, (img, flow))
# except Empty: # except Empty:
# continue # continue
@@ -635,16 +635,16 @@ class Sensor:
# img, flow = self._flow_queue.get(timeout=0.1) # img, flow = self._flow_queue.get(timeout=0.1)
# # Set __flow to reuse __compute_hhd() # # Set __flow to reuse __compute_hhd()
# self.__flow = flow # self.flow = flow
# self.__compute_hhd() # self.__compute_hhd()
# # Get results from internal variables set by __compute_hhd() # # Get results from internal variables set by __compute_hhd()
# result = { # result = {
# 'img': img, 'flow': flow, # 'img': img, 'flow': flow,
# 'normal': self.__vnormal.copy() if self.__vnormal 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, # 'shear': self.vshear.copy() if self.vshear is not None else None,
# # 'depth': getFilteredDensity(flow, use_cuda=self.cuda), # # 'depth': getFilteredDensity(flow, use_cuda=self.cuda),
# 'fnormal': self.__fnormal, 'fshearx': self.__fshearx, # 'fnormal': self.fnormal, 'fshearx': self.fshearx,
# 'fsheary': self.__fsheary, 'ftor': self.__ftor, # 'fsheary': self.fsheary, 'ftor': self.ftor,
# } # }
# # Always include contact fields to ensure consistent result structure # # 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) cv2.circle(image, (cx, cy), 10, point_color, 2)
return image return image