diff --git a/Readme.md b/Readme.md index d67c343..63b40b3 100644 --- a/Readme.md +++ b/Readme.md @@ -5,12 +5,12 @@ 1. 根据平台和python 版本下载对应的`.whl`文件 2. 运行pip命令安装: ```bash -pip install orisys-{version}-{python}-none-{os}_amd64.whl +pip install orisys-{version}-{python-version}-none-{os}_amd64.whl ``` 3. 并测试导入: ```python import orisys -orisys.__version__ +print(orisys.__version__) ``` --- ## `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() ``` --- -## 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` 方法 - -### 描述 -导出当前传感器实例使用的配置参数字典。 - -### 输入参数 -* **config_name**: 需要导出的配置参数字典名字`str` -* **path**: 导出的路径,默认`"./config/exported_config.json"` - -### 返回 - -* `dict`: 导出的配置参数字典。 - ---- -## 2. `list_config` 方法 +## 1. `list_config` 方法 ### 描述 列出内置传感器配置参数字典。 @@ -210,6 +196,23 @@ flow = sensor.read_info(sensor.info.VRAW) ### 返回 * `list`: 内置传感器配置参数字典的名字列表。 +--- +## 2. `export_config` 方法 + +### 描述 +导出当前传感器实例使用的配置参数字典。 + +### 输入参数 +* **config_name**: 需要导出的配置参数字典名字,`str` +* **path**: 导出的路径,默认`"./config/exported_config.json"` + +### 返回 + +* `dict`: 导出的配置参数字典。 + +### 保存 + +* 导出内置的`config_name`配置参数字典至`path` --- --- diff --git a/build_multi_version.py b/build_multi_version.py index 8a116f6..75e874c 100644 --- a/build_multi_version.py +++ b/build_multi_version.py @@ -11,6 +11,9 @@ import platform from pathlib import Path +# Package version +VERSION = "0.2.0" + # Python versions to build for 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) # 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: if os.path.exists(file): print(f"Copying {file}...") @@ -217,7 +220,7 @@ for pkg in packages: setup( name="orisys", - version="0.2.0", + version="{VERSION}", description="Orisys - Optical Tactile Sensor SDK (Protected)", author="Your Name", package_dir={{"": "src"}}, @@ -335,7 +338,7 @@ def build_wheel_for_version(python_version): return False 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 @@ -444,7 +447,7 @@ def main(): for wheel in Path(f'dist/{PLATFORM_NAME}').glob('*.whl'): print(f" - {wheel.name}") 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__': diff --git a/src/orisys/__init__.py b/src/orisys/__init__.py index 202c791..a3a6ffb 100644 --- a/src/orisys/__init__.py +++ b/src/orisys/__init__.py @@ -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' +] diff --git a/src/orisys/configs/__init__.py b/src/orisys/configs/__init__.py index b89735e..c4e5360 100644 --- a/src/orisys/configs/__init__.py +++ b/src/orisys/configs/__init__.py @@ -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 - \ No newline at end of file + return json.dumps(value, ensure_ascii=False) \ No newline at end of file diff --git a/src/orisys/sensor.py b/src/orisys/sensor.py index 5a22a0a..aa48120 100644 --- a/src/orisys/sensor.py +++ b/src/orisys/sensor.py @@ -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 diff --git a/src/orisys/util.py b/src/orisys/util.py index 9df36d4..cbeb8cd 100644 --- a/src/orisys/util.py +++ b/src/orisys/util.py @@ -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 +