# Fisica SDK - Python Interface
[](https://python.org)
[](LICENSE)
[](https://github.com/fisica/fisica_sdk)
### [[Readme_ko (한국어 버전)]](doc/Readme_ko.md)
---
This SDK provides a Python interface to communicate with the Fisica scale device for plantar pressure measurement analysis.
## 🚀 Installation
### Requirements
- Python 3.8 or higher
- Operating System: Windows 10/11 or macOS 10.15+
### Install via pip
```bash
pip install fisica
```
### Install from source
```bash
git clone https://github.com/care-co/fisica_sdk.git
cd fisica_sdk
pip install -e .
```
### Dependencies
The SDK automatically installs the following dependencies:
```bash
'numpy>=1.21',
'opencv-python>=4.5',
'requests>=2.25',
'packaging>=21.0',
'Pillow>=8.0',
'pyserial>=3.5',
'bleak>=0.22.3' # For Bluetooth support
# Dependencies for GUI
'PyQt5>=5.15',
```
## 🔧 Quick Start
### Basic Usage
```python
# examples/basic_use.py
import fisica_sdk as fisica
# Initialize SDK
sdk = fisica.FisicaSDK()
# Scan for devices
devices = sdk.scan_devices()
print("Available devices:", devices)
# Connect to the first device
if devices:
sdk.connect(devices[0])
# Set metadata
sdk.set_metadata(name="John Doe")
# Start measurement for 10 seconds
sdk.start_measurement(duration=10)
# Wait for completion
sdk.wait()
# Analyze results
report = sdk.analyze()
print("Analysis complete:", report)
# Disconnect
sdk.disconnect()
# Wait for completion
sdk.wait()
```
### Real-time Data Monitoring
```python
# examples/realtime_monitoring.py
import sys
import numpy as np
import cv2
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QPixmap, QImage
import fisica_sdk as fisica
class MonitoringThread(QThread):
frame_received = pyqtSignal(object)
status_updated = pyqtSignal(str)
def __init__(self, sdk):
super().__init__()
self.sdk = sdk
self._is_running = True
def run(self):
try:
self.status_updated.emit('Scanning for devices...')
devices = self.sdk.scan_devices()
if not devices:
self.status_updated.emit('No devices found.')
return
self.status_updated.emit(f"Connecting to {devices[0]['name']}...")
self.sdk.connect(devices[0])
self.sdk.on_data(self.on_frame)
self.status_updated.emit('Measurement started.')
self.sdk.start_measurement()
# Keep the thread alive while measuring
while self._is_running:
self.msleep(100)
except Exception as e:
self.status_updated.emit(f'Error: {e}')
finally:
self.sdk.stop_measurement()
self.status_updated.emit('Measurement stopped.')
def on_frame(self, frame):
if self._is_running:
self.frame_received.emit(frame)
def stop(self):
self._is_running = False
self.wait() # Wait for the run loop to finish
class RealtimeMonitor(QMainWindow):
def __init__(self, scale=2.0):
super().__init__()
self.sdk = fisica.FisicaSDK()
self.monitoring_thread = None
self.scale = scale
self.init_ui()
self.start_monitoring()
def init_ui(self):
self.setWindowTitle('Fisica SDK - Realtime Monitor')
base_img_w, base_img_h = 228, 256
img_w = int(base_img_w * self.scale)
img_h = int(base_img_h * self.scale)
window_w = img_w + 24
window_h = img_h + 80
self.setGeometry(100, 100, window_w, window_h)
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
self.image_label = QLabel('Initializing...')
self.image_label.setMinimumSize(img_w, img_h)
self.image_label.setStyleSheet("border: 1px solid black;")
self.image_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.image_label)
self.status_label = QLabel('Ready')
self.status_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.status_label)
def start_monitoring(self):
self.monitoring_thread = MonitoringThread(self.sdk)
self.monitoring_thread.frame_received.connect(self.update_visualization)
self.monitoring_thread.status_updated.connect(self.set_status)
self.monitoring_thread.start()
def set_status(self, text):
self.status_label.setText(text)
def update_visualization(self, frame):
try:
image_array = self.sdk.render(frame, mode="BLUR", bbox=True, scale=self.scale)
self.set_image(image_array)
except Exception as e:
error_message = f'Visualization error: {e}'
self.set_status(error_message)
def set_image(self, image: np.ndarray):
try:
if image.dtype != np.uint8:
if image.max() <= 1.0:
image = (image * 255).astype(np.uint8)
else:
image = np.clip(image, 0, 255).astype(np.uint8)
if len(image.shape) == 3 and image.shape[2] == 3:
h, w, ch = image.shape
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
qimage = QImage(image_rgb.data, w, h, ch * w, QImage.Format_RGB888)
else:
# Handle other formats if necessary, or raise an error
raise ValueError("Unsupported image format for display.")
pixmap = QPixmap.fromImage(qimage)
scaled_pixmap = pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.image_label.setPixmap(scaled_pixmap)
except Exception as e:
self.set_status(f'Image display error: {e}')
def closeEvent(self, event):
if self.monitoring_thread and self.monitoring_thread.isRunning():
self.monitoring_thread.stop()
event.accept()
def main():
app = QApplication(sys.argv)
window = RealtimeMonitor(scale=2.0)
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
```
### Batch Processing
```python
# examples/batch_processing.py
import fisica_sdk as fisica
sdk = fisica.FisicaSDK()
devices = sdk.scan_devices()
if devices:
sdk.connect(devices[0])
# Multiple measurements
for i in range(5):
sdk.set_metadata(id=i, name=f"Test_{i}")
sdk.start_measurement(duration=5)
sdk.wait()
sdk.analyze()
# Get session data
frames = sdk.get_session_frames()
print(f"Session {i}: {len(frames)} frames captured")
# Reset for next measurement
sdk.reset_session()
# Export all reports
reports = sdk.get_all_reports()
sdk.export_report(reports, output="measurement_results.json")
sdk.disconnect()
```
## 📱 Device Connection
### Serial Connection
The SDK automatically detects Fisica devices connected via USB.
### Bluetooth Connection
For Bluetooth(BLE) devices:
1. **Windows**: Ensure Bluetooth is enabled
2. **macOS**: Grant Bluetooth permissions when prompted
```
## 📊 Data Analysis
### Available Metrics
The SDK provides comprehensive foot pressure analysis:
- **Pressure Distribution**: Heat maps analysis
- **Center of Pressure (COP)**: Movement tracking
- **Foot Size**: Length and width measurements
- **Weight Distribution**: Left/right foot balance
- **Temporal Analysis**: Pressure changes over time
### Visualization Options
```python
# Different rendering modes
modes = ["PIXEL", "BLUR", "BINARY", "BINARY_NONZERO", "BBOX", "CONTOUR"]
for mode in modes:
image = sdk.render(frame, mode=mode, bbox=False, scale=2.0)
# Save or display image
```
## 🎯 Examples
### Command Line Interface
```python
# examples/cli_measurement.py
import fisica_sdk as fisica
import argparse
def main():
parser = argparse.ArgumentParser(description='Fisica measurement CLI')
parser.add_argument('--duration', type=int, default=10, help='Measurement duration')
parser.add_argument('--output', type=str, default='results.json', help='Output file')
args = parser.parse_args()
sdk = fisica.FisicaSDK()
devices = sdk.scan_devices()
if not devices:
print("No devices found")
return
sdk.connect(devices[0])
sdk.start_measurement(duration=args.duration)
sdk.wait()
report = sdk.analyze()
print(report)
sdk.export_report(output=args.output)
if __name__ == "__main__":
main()
```
### GUI Application
```python
# examples/gui_app.py
import sys
import numpy as np
import cv2
import fisica_sdk as fisica
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel, QTextEdit
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QPixmap, QImage
class MeasurementThread(QThread):
frame_received = pyqtSignal(object)
def __init__(self, sdk):
super().__init__()
self.sdk = sdk
self.sdk.on_data(self.on_frame)
def on_frame(self, frame):
self.frame_received.emit(frame)
def run(self):
self.sdk.start_measurement(duration=10)
self.sdk.wait()
class FisicaGUI(QMainWindow):
def __init__(self):
super().__init__()
self.sdk = fisica.FisicaSDK()
self.devices = []
self.measurement_thread = None
self.init_ui()
def init_ui(self):
self.setWindowTitle('Fisica SDK GUI')
self.setGeometry(100, 100, 800, 600)
# Central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Status label
self.status_label = QLabel('Ready')
layout.addWidget(self.status_label)
# Buttons
self.scan_btn = QPushButton('Scan Devices')
self.scan_btn.clicked.connect(self.scan_devices)
layout.addWidget(self.scan_btn)
self.connect_btn = QPushButton('Connect')
self.connect_btn.clicked.connect(self.connect_device)
self.connect_btn.setEnabled(False)
layout.addWidget(self.connect_btn)
self.start_btn = QPushButton('Start Measurement')
self.start_btn.clicked.connect(self.start_measurement)
self.start_btn.setEnabled(False)
layout.addWidget(self.start_btn)
# Device list
self.device_text = QTextEdit()
self.device_text.setMaximumHeight(100)
layout.addWidget(self.device_text)
# Visualization area
self.image_label = QLabel('Pressure visualization will appear here')
self.image_label.setMinimumHeight(400)
self.image_label.setStyleSheet("border: 1px solid black;")
self.image_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.image_label)
def scan_devices(self):
self.status_label.setText('Scanning devices...')
self.devices = self.sdk.scan_devices()
if self.devices:
device_info = '\n'.join([f"{i}: [{d['type']}] {d['name']} - {d['id']}"
for i, d in enumerate(self.devices)])
self.device_text.setText(device_info)
self.connect_btn.setEnabled(True)
self.status_label.setText(f'Found {len(self.devices)} devices')
else:
self.device_text.setText('No devices found')
self.status_label.setText('No devices found')
def connect_device(self):
if self.devices:
try:
self.sdk.connect(self.devices[0]) # Connect to first device
self.status_label.setText(f'Connected to {self.devices[0]["name"]}')
self.start_btn.setEnabled(True)
self.connect_btn.setEnabled(False)
except Exception as e:
self.status_label.setText(f'Connection failed: {e}')
def start_measurement(self):
if self.measurement_thread and self.measurement_thread.isRunning():
return
self.status_label.setText('Starting measurement...')
self.start_btn.setEnabled(False)
self.measurement_thread = MeasurementThread(self.sdk)
self.measurement_thread.frame_received.connect(self.update_visualization)
self.measurement_thread.finished.connect(self.measurement_finished)
self.measurement_thread.start()
def set_image(self, image: np.ndarray):
"""Improved image display method based on the provided code"""
try:
if len(image.shape) == 2:
# Grayscale image
h, w = image.shape
qimage = QImage(image.data, w, h, w, QImage.Format_Grayscale8)
elif len(image.shape) == 3:
if image.shape[2] == 3:
# RGB/BGR image
h, w, ch = image.shape
# Convert BGR to RGB if needed
if image.dtype == np.uint8:
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
else:
image_rgb = image
qimage = QImage(image_rgb.data, w, h, ch * w, QImage.Format_RGB888)
elif image.shape[2] == 4:
# RGBA image
image = cv2.cvtColor(image, cv2.COLOR_BGRA2RGBA)
h, w, ch = image.shape
qimage = QImage(image.data, w, h, ch * w, QImage.Format_RGBA8888)
else:
raise ValueError("Unsupported image format.")
else:
raise ValueError("Unsupported image format.")
pixmap = QPixmap.fromImage(qimage)
# Scale to fit the label while maintaining aspect ratio
scaled_pixmap = pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.image_label.setPixmap(scaled_pixmap)
except Exception as e:
print(f"Image display error: {e}")
self.status_label.setText(f'Image display error: {e}')
def update_visualization(self, frame):
try:
# Render the frame
image_array = self.sdk.render(frame, mode="BLUR", scale=2.0)
# Ensure the image is in the correct format
if image_array.dtype != np.uint8:
# Normalize to 0-255 range if needed
if image_array.max() <= 1.0:
image_array = (image_array * 255).astype(np.uint8)
else:
image_array = np.clip(image_array, 0, 255).astype(np.uint8)
# Use the improved image display method
self.set_image(image_array)
except Exception as e:
print(f"Visualization error: {e}")
self.status_label.setText(f'Visualization error: {e}')
def measurement_finished(self):
self.status_label.setText('Measurement completed')
self.start_btn.setEnabled(True)
# Analyze results
try:
report = self.sdk.analyze()
print("Analysis completed:", report)
except Exception as e:
print(f"Analysis error: {e}")
def main():
app = QApplication(sys.argv)
window = FisicaGUI()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
```
## 🔧 Advanced Configuration
### Async Operations
```python
# Non-blocking measurements
sdk.start_measurement(duration=30)
# Do other work while measuring
for i in range(10):
sdk.sleep(2, after=lambda: print(f"Checkpoint {i}"))
# Wait for completion
sdk.wait()
```
## 🐛 Troubleshooting
### Common Issues
1. **Device not found**
```python
devices = sdk.scan_devices()
if not devices:
print("No devices found. Check connection and permissions.")
```
2. **Bluetooth permissions (macOS)**
- Grant Bluetooth access when prompted
### Debugging
Enable debug logging:
```python
import logging
sdk = fisica.FisicaSDK(debug=logging.DEBUG)
# could be replaced logging.DEBUG to 10
```
## 📚 API Reference
---
### `set_metadata(**kwargs)`
**Description**:
Sets metadata for the current measurement session.
**Parameters**:
- `id` (Any, optional): Identifier for the user/session.
- `name` (str, optional): Identifier for the user/session.
**Returns**:
None
---
### `scan_devices()`
**Description**:
Scans for available scale devices via serial or Bluetooth connection.
Returns wired device information immediately if a wired device is connected,
otherwise searches for powered-on scale devices via Bluetooth.
**Parameters**:
None
**Returns**:
- `List[Dict]`: List of device dictionaries, each containing connection details.
---
### `connect(device: Dict)`
**Description**:
Establishes connection to the selected device returned by `scan_devices()`.
**Parameters**:
- `device` (Dict): A dictionary containing device ID and type.
**Returns**:
None
---
### `disconnect()`
**Description**:
Disconnects from the connected scale device. Automatically waits for measurement operations to complete before closing the connection.
**Parameters**:
None
**Returns**:
None
---
### `start_measurement(duration: Optional[float])`
**Description**:
Begins capturing measurement data. If `duration` is given, it stops automatically after the duration (in seconds).
If `duration` is not given, the measurement will not stop automatically. This is not the intended operation,
so it is not recommended to run the measurement for too long without a duration.
Set a `duration`, or call the SDK's built-in `sleep()` function or the `sleep()` function from the `time` package to measure for a certain period,
and then call `stop_measurement()` to end the measurement. The SDK's built-in sleep does not affect the main thread execution.
Alternatively, you can develop a GUI-based application to automatically end the measurement by `duration` or set up a trigger for `stop_measurement()`.
**Parameters**:
- `duration` (float, optional): Duration of the measurement in seconds.
**Returns**:
None
---
### `stop_measurement()`
**Description**:
Stops the current measurement.
**Parameters**:
None
**Returns**:
None
---
### `on_data(callback)`
**Description**:
Registers a callback function that will be called in real-time as measurement frames are received.
**Parameters**:
- `callback` (callable): You can register a function that takes a single argument of type `MeasurementFrame`.
**Returns**:
None
---
### `sleep(seconds: float, after: Optional[callable])`
**Description**:
Asynchronously sleeps for the given time without blocking the main thread. An optional callback can be executed afterward.
**Parameters**:
- `seconds` (float): Number of seconds to sleep.
- `after` (callable, optional): Function to call after sleeping.
**Returns**:
None
---
### `wait()`
**Description**:
Waits until all asynchronous operations (e.g., `start_measurement()`, `stop_measurement()`, `sleep()`, `disconnect()`) are completed.
This ensures that all SDK operations that run in the background are fully finished before proceeding to the next step, such as data processing or visualization.
⚠️ It **blocks the main thread** while waiting.
**Parameters**:
None
**Returns**:
None
---
### `analyze()`
**Description**:
Analyzes the current session's captured frames and computes relevant metrics such as foot size and pressure distribution.
This function returns values only immediately after a measurement, and may not work as expected afterward.
To get the most recent report of the current session, it is recommended to call `get_current_report()`.
Please make sure to call `analyze()` before loading or exporting reports.
**Parameters**:
None
**Returns**:
- `Dict`: Dictionary containing metadata, sensor grid, weight, etc.
---
### `get_session_frames()`
**Description**:
Returns the frame data of the current session in serialized format.
**Parameters**:
None
**Returns**:
- `List[Dict]`: Serialized frames from the current session.
---
### `get_report()`
**Description**:
Returns the most recently analyzed session report.
**Parameters**:
None
**Returns**:
- `Dict`: Dictionary containing metadata, sensor grid, and computed results.
---
### `get_all_reports()`
**Description**:
Retrieves analysis reports from all completed sessions.
**Parameters**:
None
**Returns**:
- `List[Dict]`: A list of session reports.
---
### `export_report(report: Optional[Union['SessionReport', list]], output: Optional[str])`
**Description**:
Exports all reports to a JSON file at the specified output path, filename, or directory.
Creates missing directories automatically and appends .json extension if not provided
If target is an existing directory, generates filename using predefined naming rules.
e.g., "output/sample/result" becomes "output/sample/result.json" if result folder doesn't exist.
If no argument is provided for report exports information from the most recently analyzed SessionReport.
Calling this method without having performed `analyze()` may result in an error.
**Parameters**:
- `report` (SessionReport, optional): Data obtained through `get_all_reports()` or `get_reports()`. If not provided, outputs based on the most recent SessionReport.
- `output` (str, optional): File path or directory for export.
**Returns**:
None
---
### `reset_session()`
**Description**:
Clears the current session (frames and metadata).
**Parameters**:
None
**Returns**:
None
---
### `reset_reports()`
**Description**:
Clears all stored analysis reports from memory.
**Parameters**:
None
**Returns**:
None
---
### `render(frame: Optional[MeasurementFrame], grid_data: Optional[np.ndarray], mode: Optional[str], scale: Optional[float])`
**Description**:
Renders a sensor grid (from a frame or raw grid data) into an image using the specified visualization mode and scale.
`frame` is the variable passed as an argument when a callback function is registered to `on_data`.
**Parameters**:
- `frame` (MeasurementFrame, optional): A dataclass instance containing the sensor matrix.
- `grid_data` (np.ndarray, optional): A 2D NumPy array representing the sensor grid.
- `mode` (str, optional): Visualization style (e.g., "BLUR"). For more details, refer to the VisualOption section below. `default:VisualOptions.BLUR`
- `scale` (float, optional): Scale factor for the image size. `default:1.0`
**Returns**:
- `np.ndarray`: Rendered image as a NumPy array.
---
**🖼️ VisualOptions**
The VisualOptions class defines available rendering modes for visualizing sensor data using FisicaSDK.render().
Rendering modes:
- PIXEL: Pixel heatmap rendering
- BLUR: Smooth heatmap rendering
- BINARY: Binary grid rendering
- BINARY_NONZERO: Binary grid rendering excluding zero values
- BBOX: Bounding box rendering based on BLUR and Principal Component Analysis (PCA)
- CONTOUR: Pixel-based contour rendering
- ALL: Returns all rendering results in list format
---
### `run(scale: Optional[float])`
**Description**:
Launches the GUI viewer for real-time visualizations with the specified scale.
**Parameters**:
- `scale` (float, optional): Scale factor for the GUI window. `default:1.0`
**Returns**:
None
---
### `update(image: np.ndarray)`
**Description**:
Updates the GUI viewer with the given rendered image.
**Parameters**:
- `image` (np.ndarray): The rendered image to display.
**Returns**:
None
---
## 📚 API Reference - Additional Methods
### `set_zero()`
**Description**:
Calibrates the zero point of the device by setting the current reading as the zero reference point for weight measurements. This is equivalent to the tare function on digital scales and should be used when the scale platform is empty to establish a proper baseline.
**Parameters**:
None
**Returns**:
None
**Usage Example**:
```python
# Connect to device first
devices = sdk.scan_devices()
if devices:
sdk.connect(devices[0])
# Ensure platform is empty, then set zero point
sdk.set_zero() # Tare the scale
# Now ready for accurate measurements
sdk.start_measurement(duration=10)
# Wait for finish measurement
sdk.wait()
```
**Notes**:
- Device connection is required before calling this method
- Call `connect()` first to establish device connection
- Use this function when the scale platform is empty
- This establishes the baseline for all subsequent weight measurements
---
### `set_scale(value)`
**Description**:
Sets the scale calibration parameter of the device. This function adjusts the internal calibration value used by the device's weight measurement system to ensure accurate readings.
**Parameters**:
- `value` (int): Scale calibration parameter, must be within the range -32768 to 32767
**Returns**:
None
**Usage Example**:
```python
# Connect to device first
devices = sdk.scan_devices()
if devices:
sdk.connect(devices[0])
# Set zero point first
sdk.set_zero()
# Set scale calibration parameter
sdk.set_scale(12600) # Set calibration value to 12600
# Start measurement with calibrated settings
sdk.start_measurement(duration=10)
sdk.wait()
```
**Notes**:
- Device connection is required before calling this method
- Call `connect()` first to establish device connection
- Value must be an integer within the range -32768 to 32767
- Use after `set_zero()` for optimal calibration
- Consult device documentation for appropriate calibration values
---
## 🔧 Device Calibration Workflow
For optimal measurement accuracy, follow this calibration sequence:
```python
# examples/calibration.py
import fisica_sdk as fisica
# Initialize and connect
sdk = fisica.FisicaSDK()
devices = sdk.scan_devices()
if devices:
sdk.connect(devices[0])
# Step 1: Set zero point (empty platform)
print("Please ensure platform is empty...")
input("Press Enter when ready...")
sdk.set_zero()
print("Zero point calibrated.")
# Step 2: Set scale calibration parameter
# Use appropriate calibration value for your device
# (consult device documentation for recommended values)
calibration_value = 12600 # Example value within -32768 to 32767 range
sdk.set_scale(calibration_value)
print(f"Scale calibration set to: {calibration_value}")
# Step 3: Verify calibration with known reference weight
print("Place a known reference weight on platform...")
reference_weight = float(input("Enter reference weight (kg): "))
# Take a measurement to verify accuracy
sdk.start_measurement(duration=10)
sdk.wait()
report = sdk.analyze()
measured_weight = report.weight
print(f"Reference: {reference_weight}kg, Measured: {measured_weight}kg")
print(f"Accuracy: {abs(measured_weight - reference_weight):.3f}kg difference")
# Step 4: Ready for accurate measurements
print("Calibration complete. Ready for measurements.")
sdk.disconnect()
```
---
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 📞 Support
- **Issues**: [GitHub Issues](https://github.com/care-co/fisica_sdk/issues)
- **Email**: carencoinc@carenco.kr
- **Website**: [https://carenco.kr](https://carenco.kr/en)
## 🔄 Changelog
### v1.0.0 (Latest)
- Initial release
- Serial and Bluetooth device support
- Real-time data visualization
- Comprehensive pressure analysis
---
Raw data
{
"_id": null,
"home_page": "https://github.com/Care-Co/",
"name": "fisica",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "foot pressure analysis medical sensor visualization",
"author": "Care&Co",
"author_email": "carencoinc@carenco.kr",
"download_url": "https://files.pythonhosted.org/packages/1b/b8/8c33774c8a2d39dccbe1bc4c8ff515f523a7ce708a135e83e136106b47bf/fisica-1.0.1.tar.gz",
"platform": null,
"description": "# Fisica SDK - Python Interface\n\n[](https://python.org)\n[](LICENSE)\n[](https://github.com/fisica/fisica_sdk)\n\n\n### [[Readme_ko (\ud55c\uad6d\uc5b4 \ubc84\uc804)]](doc/Readme_ko.md)\n\n---\n\nThis SDK provides a Python interface to communicate with the Fisica scale device for plantar pressure measurement analysis.\n\n## \ud83d\ude80 Installation\n\n### Requirements\n\n- Python 3.8 or higher\n- Operating System: Windows 10/11 or macOS 10.15+\n\n### Install via pip\n\n```bash\n pip install fisica\n```\n\n### Install from source\n\n```bash\n git clone https://github.com/care-co/fisica_sdk.git\n cd fisica_sdk\n pip install -e .\n```\n\n### Dependencies\n\nThe SDK automatically installs the following dependencies:\n\n```bash\n 'numpy>=1.21',\n 'opencv-python>=4.5',\n 'requests>=2.25',\n 'packaging>=21.0',\n 'Pillow>=8.0',\n 'pyserial>=3.5',\n 'bleak>=0.22.3' # For Bluetooth support\n\n # Dependencies for GUI\n 'PyQt5>=5.15',\n```\n\n## \ud83d\udd27 Quick Start\n\n### Basic Usage\n\n```python\n# examples/basic_use.py\nimport fisica_sdk as fisica\n\n# Initialize SDK\nsdk = fisica.FisicaSDK()\n\n# Scan for devices\ndevices = sdk.scan_devices()\nprint(\"Available devices:\", devices)\n\n# Connect to the first device\nif devices:\n sdk.connect(devices[0])\n\n # Set metadata\n sdk.set_metadata(name=\"John Doe\")\n\n # Start measurement for 10 seconds\n sdk.start_measurement(duration=10)\n\n # Wait for completion\n sdk.wait()\n\n # Analyze results\n report = sdk.analyze()\n print(\"Analysis complete:\", report)\n\n # Disconnect\n sdk.disconnect()\n\n # Wait for completion\n sdk.wait()\n```\n\n### Real-time Data Monitoring\n\n```python\n# examples/realtime_monitoring.py\nimport sys\nimport numpy as np\nimport cv2\nfrom PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel\nfrom PyQt5.QtCore import QThread, pyqtSignal, Qt\nfrom PyQt5.QtGui import QPixmap, QImage\nimport fisica_sdk as fisica\n\nclass MonitoringThread(QThread):\n frame_received = pyqtSignal(object)\n status_updated = pyqtSignal(str)\n\n def __init__(self, sdk):\n super().__init__()\n self.sdk = sdk\n self._is_running = True\n\n def run(self):\n try:\n self.status_updated.emit('Scanning for devices...')\n devices = self.sdk.scan_devices()\n\n if not devices:\n self.status_updated.emit('No devices found.')\n return\n\n self.status_updated.emit(f\"Connecting to {devices[0]['name']}...\")\n self.sdk.connect(devices[0])\n self.sdk.on_data(self.on_frame)\n\n self.status_updated.emit('Measurement started.')\n self.sdk.start_measurement()\n\n # Keep the thread alive while measuring\n while self._is_running:\n self.msleep(100)\n\n except Exception as e:\n self.status_updated.emit(f'Error: {e}')\n finally:\n self.sdk.stop_measurement()\n self.status_updated.emit('Measurement stopped.')\n\n def on_frame(self, frame):\n if self._is_running:\n self.frame_received.emit(frame)\n\n def stop(self):\n self._is_running = False\n self.wait() # Wait for the run loop to finish\n\nclass RealtimeMonitor(QMainWindow):\n def __init__(self, scale=2.0):\n super().__init__()\n self.sdk = fisica.FisicaSDK()\n self.monitoring_thread = None\n self.scale = scale\n self.init_ui()\n self.start_monitoring()\n\n def init_ui(self):\n self.setWindowTitle('Fisica SDK - Realtime Monitor')\n\n base_img_w, base_img_h = 228, 256\n img_w = int(base_img_w * self.scale)\n img_h = int(base_img_h * self.scale)\n\n window_w = img_w + 24\n window_h = img_h + 80\n\n self.setGeometry(100, 100, window_w, window_h)\n\n central_widget = QWidget()\n self.setCentralWidget(central_widget)\n layout = QVBoxLayout(central_widget)\n\n self.image_label = QLabel('Initializing...')\n self.image_label.setMinimumSize(img_w, img_h)\n self.image_label.setStyleSheet(\"border: 1px solid black;\")\n self.image_label.setAlignment(Qt.AlignCenter)\n layout.addWidget(self.image_label)\n\n self.status_label = QLabel('Ready')\n self.status_label.setAlignment(Qt.AlignCenter)\n layout.addWidget(self.status_label)\n\n def start_monitoring(self):\n self.monitoring_thread = MonitoringThread(self.sdk)\n self.monitoring_thread.frame_received.connect(self.update_visualization)\n self.monitoring_thread.status_updated.connect(self.set_status)\n self.monitoring_thread.start()\n\n def set_status(self, text):\n self.status_label.setText(text)\n\n def update_visualization(self, frame):\n try:\n image_array = self.sdk.render(frame, mode=\"BLUR\", bbox=True, scale=self.scale)\n self.set_image(image_array)\n except Exception as e:\n error_message = f'Visualization error: {e}'\n self.set_status(error_message)\n\n def set_image(self, image: np.ndarray):\n try:\n if image.dtype != np.uint8:\n if image.max() <= 1.0:\n image = (image * 255).astype(np.uint8)\n else:\n image = np.clip(image, 0, 255).astype(np.uint8)\n\n if len(image.shape) == 3 and image.shape[2] == 3:\n h, w, ch = image.shape\n image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n qimage = QImage(image_rgb.data, w, h, ch * w, QImage.Format_RGB888)\n else:\n # Handle other formats if necessary, or raise an error\n raise ValueError(\"Unsupported image format for display.\")\n\n pixmap = QPixmap.fromImage(qimage)\n scaled_pixmap = pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)\n self.image_label.setPixmap(scaled_pixmap)\n except Exception as e:\n self.set_status(f'Image display error: {e}')\n\n def closeEvent(self, event):\n if self.monitoring_thread and self.monitoring_thread.isRunning():\n self.monitoring_thread.stop()\n event.accept()\n\ndef main():\n app = QApplication(sys.argv)\n window = RealtimeMonitor(scale=2.0)\n window.show()\n sys.exit(app.exec_())\n\nif __name__ == '__main__':\n main()\n```\n\n### Batch Processing\n\n```python\n# examples/batch_processing.py\nimport fisica_sdk as fisica\n\nsdk = fisica.FisicaSDK()\ndevices = sdk.scan_devices()\n\nif devices:\n sdk.connect(devices[0])\n\n # Multiple measurements\n for i in range(5):\n sdk.set_metadata(id=i, name=f\"Test_{i}\")\n sdk.start_measurement(duration=5)\n sdk.wait()\n sdk.analyze()\n # Get session data\n frames = sdk.get_session_frames()\n print(f\"Session {i}: {len(frames)} frames captured\")\n\n # Reset for next measurement\n sdk.reset_session()\n\n # Export all reports\n reports = sdk.get_all_reports()\n sdk.export_report(reports, output=\"measurement_results.json\")\n sdk.disconnect()\n```\n\n## \ud83d\udcf1 Device Connection\n\n### Serial Connection\nThe SDK automatically detects Fisica devices connected via USB.\n\n### Bluetooth Connection\nFor Bluetooth(BLE) devices:\n\n1. **Windows**: Ensure Bluetooth is enabled\n2. **macOS**: Grant Bluetooth permissions when prompted\n ```\n\n## \ud83d\udcca Data Analysis\n\n### Available Metrics\n\nThe SDK provides comprehensive foot pressure analysis:\n\n- **Pressure Distribution**: Heat maps analysis\n- **Center of Pressure (COP)**: Movement tracking\n- **Foot Size**: Length and width measurements\n- **Weight Distribution**: Left/right foot balance\n- **Temporal Analysis**: Pressure changes over time\n\n### Visualization Options\n\n```python\n# Different rendering modes\nmodes = [\"PIXEL\", \"BLUR\", \"BINARY\", \"BINARY_NONZERO\", \"BBOX\", \"CONTOUR\"]\n\nfor mode in modes:\n image = sdk.render(frame, mode=mode, bbox=False, scale=2.0)\n # Save or display image\n```\n\n## \ud83c\udfaf Examples\n\n### Command Line Interface\n\n```python\n# examples/cli_measurement.py\nimport fisica_sdk as fisica\nimport argparse\n\ndef main():\n parser = argparse.ArgumentParser(description='Fisica measurement CLI')\n parser.add_argument('--duration', type=int, default=10, help='Measurement duration')\n parser.add_argument('--output', type=str, default='results.json', help='Output file')\n args = parser.parse_args()\n\n sdk = fisica.FisicaSDK()\n devices = sdk.scan_devices()\n\n if not devices:\n print(\"No devices found\")\n return\n\n sdk.connect(devices[0])\n sdk.start_measurement(duration=args.duration)\n sdk.wait()\n\n report = sdk.analyze()\n print(report)\n\n sdk.export_report(output=args.output)\n\nif __name__ == \"__main__\":\n main()\n```\n\n### GUI Application\n\n```python\n# examples/gui_app.py\nimport sys\nimport numpy as np\nimport cv2\nimport fisica_sdk as fisica\nfrom PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel, QTextEdit\nfrom PyQt5.QtCore import QThread, pyqtSignal, Qt\nfrom PyQt5.QtGui import QPixmap, QImage\n\nclass MeasurementThread(QThread):\n frame_received = pyqtSignal(object)\n\n def __init__(self, sdk):\n super().__init__()\n self.sdk = sdk\n self.sdk.on_data(self.on_frame)\n\n def on_frame(self, frame):\n self.frame_received.emit(frame)\n\n def run(self):\n self.sdk.start_measurement(duration=10)\n self.sdk.wait()\n\nclass FisicaGUI(QMainWindow):\n def __init__(self):\n super().__init__()\n self.sdk = fisica.FisicaSDK()\n self.devices = []\n self.measurement_thread = None\n self.init_ui()\n\n def init_ui(self):\n self.setWindowTitle('Fisica SDK GUI')\n self.setGeometry(100, 100, 800, 600)\n\n # Central widget\n central_widget = QWidget()\n self.setCentralWidget(central_widget)\n layout = QVBoxLayout(central_widget)\n\n # Status label\n self.status_label = QLabel('Ready')\n layout.addWidget(self.status_label)\n\n # Buttons\n self.scan_btn = QPushButton('Scan Devices')\n self.scan_btn.clicked.connect(self.scan_devices)\n layout.addWidget(self.scan_btn)\n\n self.connect_btn = QPushButton('Connect')\n self.connect_btn.clicked.connect(self.connect_device)\n self.connect_btn.setEnabled(False)\n layout.addWidget(self.connect_btn)\n\n self.start_btn = QPushButton('Start Measurement')\n self.start_btn.clicked.connect(self.start_measurement)\n self.start_btn.setEnabled(False)\n layout.addWidget(self.start_btn)\n\n # Device list\n self.device_text = QTextEdit()\n self.device_text.setMaximumHeight(100)\n layout.addWidget(self.device_text)\n\n # Visualization area\n self.image_label = QLabel('Pressure visualization will appear here')\n self.image_label.setMinimumHeight(400)\n self.image_label.setStyleSheet(\"border: 1px solid black;\")\n self.image_label.setAlignment(Qt.AlignCenter)\n layout.addWidget(self.image_label)\n\n def scan_devices(self):\n self.status_label.setText('Scanning devices...')\n self.devices = self.sdk.scan_devices()\n\n if self.devices:\n device_info = '\\n'.join([f\"{i}: [{d['type']}] {d['name']} - {d['id']}\"\n for i, d in enumerate(self.devices)])\n self.device_text.setText(device_info)\n self.connect_btn.setEnabled(True)\n self.status_label.setText(f'Found {len(self.devices)} devices')\n else:\n self.device_text.setText('No devices found')\n self.status_label.setText('No devices found')\n\n def connect_device(self):\n if self.devices:\n try:\n self.sdk.connect(self.devices[0]) # Connect to first device\n self.status_label.setText(f'Connected to {self.devices[0][\"name\"]}')\n self.start_btn.setEnabled(True)\n self.connect_btn.setEnabled(False)\n except Exception as e:\n self.status_label.setText(f'Connection failed: {e}')\n\n def start_measurement(self):\n if self.measurement_thread and self.measurement_thread.isRunning():\n return\n\n self.status_label.setText('Starting measurement...')\n self.start_btn.setEnabled(False)\n\n self.measurement_thread = MeasurementThread(self.sdk)\n self.measurement_thread.frame_received.connect(self.update_visualization)\n self.measurement_thread.finished.connect(self.measurement_finished)\n self.measurement_thread.start()\n\n def set_image(self, image: np.ndarray):\n \"\"\"Improved image display method based on the provided code\"\"\"\n try:\n if len(image.shape) == 2:\n # Grayscale image\n h, w = image.shape\n qimage = QImage(image.data, w, h, w, QImage.Format_Grayscale8)\n elif len(image.shape) == 3:\n if image.shape[2] == 3:\n # RGB/BGR image\n h, w, ch = image.shape\n # Convert BGR to RGB if needed\n if image.dtype == np.uint8:\n image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n else:\n image_rgb = image\n qimage = QImage(image_rgb.data, w, h, ch * w, QImage.Format_RGB888)\n elif image.shape[2] == 4:\n # RGBA image\n image = cv2.cvtColor(image, cv2.COLOR_BGRA2RGBA)\n h, w, ch = image.shape\n qimage = QImage(image.data, w, h, ch * w, QImage.Format_RGBA8888)\n else:\n raise ValueError(\"Unsupported image format.\")\n else:\n raise ValueError(\"Unsupported image format.\")\n\n pixmap = QPixmap.fromImage(qimage)\n # Scale to fit the label while maintaining aspect ratio\n scaled_pixmap = pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)\n self.image_label.setPixmap(scaled_pixmap)\n\n except Exception as e:\n print(f\"Image display error: {e}\")\n self.status_label.setText(f'Image display error: {e}')\n\n def update_visualization(self, frame):\n try:\n # Render the frame\n image_array = self.sdk.render(frame, mode=\"BLUR\", scale=2.0)\n\n # Ensure the image is in the correct format\n if image_array.dtype != np.uint8:\n # Normalize to 0-255 range if needed\n if image_array.max() <= 1.0:\n image_array = (image_array * 255).astype(np.uint8)\n else:\n image_array = np.clip(image_array, 0, 255).astype(np.uint8)\n\n # Use the improved image display method\n self.set_image(image_array)\n\n except Exception as e:\n print(f\"Visualization error: {e}\")\n self.status_label.setText(f'Visualization error: {e}')\n\n def measurement_finished(self):\n self.status_label.setText('Measurement completed')\n self.start_btn.setEnabled(True)\n\n # Analyze results\n try:\n report = self.sdk.analyze()\n print(\"Analysis completed:\", report)\n except Exception as e:\n print(f\"Analysis error: {e}\")\n\ndef main():\n app = QApplication(sys.argv)\n window = FisicaGUI()\n window.show()\n sys.exit(app.exec_())\n\nif __name__ == '__main__':\n main()\n```\n\n## \ud83d\udd27 Advanced Configuration\n\n### Async Operations\n\n```python\n# Non-blocking measurements\nsdk.start_measurement(duration=30)\n\n# Do other work while measuring\nfor i in range(10):\n sdk.sleep(2, after=lambda: print(f\"Checkpoint {i}\"))\n\n# Wait for completion\nsdk.wait()\n```\n\n## \ud83d\udc1b Troubleshooting\n\n### Common Issues\n\n1. **Device not found**\n ```python\n devices = sdk.scan_devices()\n if not devices:\n print(\"No devices found. Check connection and permissions.\")\n ```\n\n2. **Bluetooth permissions (macOS)**\n - Grant Bluetooth access when prompted\n\n### Debugging\n\nEnable debug logging:\n\n```python\nimport logging\n\nsdk = fisica.FisicaSDK(debug=logging.DEBUG)\n# could be replaced logging.DEBUG to 10\n```\n\n## \ud83d\udcda API Reference\n\n---\n\n### `set_metadata(**kwargs)`\n\n**Description**: \nSets metadata for the current measurement session.\n\n**Parameters**:\n- `id` (Any, optional): Identifier for the user/session.\n- `name` (str, optional): Identifier for the user/session.\n\n**Returns**: \nNone\n\n---\n\n### `scan_devices()`\n\n**Description**: \nScans for available scale devices via serial or Bluetooth connection. \nReturns wired device information immediately if a wired device is connected, \notherwise searches for powered-on scale devices via Bluetooth. \n\n**Parameters**: \nNone\n\n**Returns**:\n- `List[Dict]`: List of device dictionaries, each containing connection details.\n\n---\n\n### `connect(device: Dict)`\n\n**Description**: \nEstablishes connection to the selected device returned by `scan_devices()`.\n\n**Parameters**:\n- `device` (Dict): A dictionary containing device ID and type.\n\n**Returns**: \nNone\n\n---\n\n### `disconnect()`\n\n**Description**: \nDisconnects from the connected scale device. Automatically waits for measurement operations to complete before closing the connection. \n\n**Parameters**:\nNone\n\n**Returns**: \nNone\n\n---\n\n### `start_measurement(duration: Optional[float])`\n\n**Description**: \nBegins capturing measurement data. If `duration` is given, it stops automatically after the duration (in seconds). \nIf `duration` is not given, the measurement will not stop automatically. This is not the intended operation, \nso it is not recommended to run the measurement for too long without a duration. \nSet a `duration`, or call the SDK's built-in `sleep()` function or the `sleep()` function from the `time` package to measure for a certain period, \nand then call `stop_measurement()` to end the measurement. The SDK's built-in sleep does not affect the main thread execution. \nAlternatively, you can develop a GUI-based application to automatically end the measurement by `duration` or set up a trigger for `stop_measurement()`. \n\n**Parameters**:\n- `duration` (float, optional): Duration of the measurement in seconds.\n\n**Returns**: \nNone\n\n---\n\n### `stop_measurement()`\n\n**Description**: \nStops the current measurement.\n\n**Parameters**: \nNone\n\n**Returns**: \nNone\n\n---\n\n### `on_data(callback)`\n\n**Description**: \nRegisters a callback function that will be called in real-time as measurement frames are received.\n\n**Parameters**:\n- `callback` (callable): You can register a function that takes a single argument of type `MeasurementFrame`.\n\n**Returns**: \nNone\n\n---\n\n### `sleep(seconds: float, after: Optional[callable])`\n\n**Description**: \nAsynchronously sleeps for the given time without blocking the main thread. An optional callback can be executed afterward.\n\n**Parameters**:\n- `seconds` (float): Number of seconds to sleep.\n- `after` (callable, optional): Function to call after sleeping.\n\n**Returns**: \nNone\n\n---\n\n### `wait()`\n\n**Description**: \nWaits until all asynchronous operations (e.g., `start_measurement()`, `stop_measurement()`, `sleep()`, `disconnect()`) are completed.\n\nThis ensures that all SDK operations that run in the background are fully finished before proceeding to the next step, such as data processing or visualization.\n\n\u26a0\ufe0f It **blocks the main thread** while waiting.\n\n**Parameters**: \nNone\n\n**Returns**: \nNone\n\n---\n\n### `analyze()`\n\n**Description**: \nAnalyzes the current session's captured frames and computes relevant metrics such as foot size and pressure distribution.\n\nThis function returns values only immediately after a measurement, and may not work as expected afterward.\nTo get the most recent report of the current session, it is recommended to call `get_current_report()`. \nPlease make sure to call `analyze()` before loading or exporting reports. \n\n**Parameters**: \nNone\n\n**Returns**:\n- `Dict`: Dictionary containing metadata, sensor grid, weight, etc.\n\n---\n\n### `get_session_frames()`\n\n**Description**: \nReturns the frame data of the current session in serialized format.\n\n**Parameters**: \nNone\n\n**Returns**:\n- `List[Dict]`: Serialized frames from the current session.\n\n---\n\n### `get_report()`\n\n**Description**: \nReturns the most recently analyzed session report.\n\n**Parameters**: \nNone\n\n**Returns**:\n- `Dict`: Dictionary containing metadata, sensor grid, and computed results.\n\n---\n\n### `get_all_reports()`\n\n**Description**: \nRetrieves analysis reports from all completed sessions.\n\n**Parameters**: \nNone\n\n**Returns**:\n- `List[Dict]`: A list of session reports.\n\n---\n\n### `export_report(report: Optional[Union['SessionReport', list]], output: Optional[str])`\n\n**Description**: \nExports all reports to a JSON file at the specified output path, filename, or directory. \nCreates missing directories automatically and appends .json extension if not provided \nIf target is an existing directory, generates filename using predefined naming rules. \ne.g., \"output/sample/result\" becomes \"output/sample/result.json\" if result folder doesn't exist. \n\nIf no argument is provided for report exports information from the most recently analyzed SessionReport. \nCalling this method without having performed `analyze()` may result in an error. \n\n\n**Parameters**: \n\n- `report` (SessionReport, optional): Data obtained through `get_all_reports()` or `get_reports()`. If not provided, outputs based on the most recent SessionReport.\n- `output` (str, optional): File path or directory for export.\n\n**Returns**: \nNone\n\n---\n\n### `reset_session()`\n\n**Description**: \nClears the current session (frames and metadata).\n\n**Parameters**: \nNone\n\n**Returns**: \nNone\n\n---\n\n### `reset_reports()`\n\n**Description**: \nClears all stored analysis reports from memory.\n\n**Parameters**: \nNone\n\n**Returns**: \nNone\n\n---\n\n### `render(frame: Optional[MeasurementFrame], grid_data: Optional[np.ndarray], mode: Optional[str], scale: Optional[float])`\n\n**Description**: \nRenders a sensor grid (from a frame or raw grid data) into an image using the specified visualization mode and scale.\n`frame` is the variable passed as an argument when a callback function is registered to `on_data`.\n\n**Parameters**:\n- `frame` (MeasurementFrame, optional): A dataclass instance containing the sensor matrix.\n- `grid_data` (np.ndarray, optional): A 2D NumPy array representing the sensor grid.\n- `mode` (str, optional): Visualization style (e.g., \"BLUR\"). For more details, refer to the VisualOption section below. `default:VisualOptions.BLUR`\n- `scale` (float, optional): Scale factor for the image size. `default:1.0`\n\n**Returns**:\n- `np.ndarray`: Rendered image as a NumPy array.\n\n---\n\n**\ud83d\uddbc\ufe0f VisualOptions**\n\nThe VisualOptions class defines available rendering modes for visualizing sensor data using FisicaSDK.render().\n\nRendering modes:\n- PIXEL: Pixel heatmap rendering\n- BLUR: Smooth heatmap rendering\n- BINARY: Binary grid rendering\n- BINARY_NONZERO: Binary grid rendering excluding zero values\n- BBOX: Bounding box rendering based on BLUR and Principal Component Analysis (PCA)\n- CONTOUR: Pixel-based contour rendering\n- ALL: Returns all rendering results in list format\n\n---\n\n### `run(scale: Optional[float])`\n\n**Description**: \nLaunches the GUI viewer for real-time visualizations with the specified scale.\n\n**Parameters**:\n- `scale` (float, optional): Scale factor for the GUI window. `default:1.0`\n\n**Returns**: \nNone\n\n---\n\n### `update(image: np.ndarray)`\n\n**Description**: \nUpdates the GUI viewer with the given rendered image.\n\n**Parameters**:\n- `image` (np.ndarray): The rendered image to display.\n\n**Returns**:\nNone\n\n---\n## \ud83d\udcda API Reference - Additional Methods\n\n\n### `set_zero()`\n\n**Description**: \nCalibrates the zero point of the device by setting the current reading as the zero reference point for weight measurements. This is equivalent to the tare function on digital scales and should be used when the scale platform is empty to establish a proper baseline.\n\n**Parameters**: \nNone\n\n**Returns**: \nNone\n\n**Usage Example**:\n```python\n# Connect to device first\ndevices = sdk.scan_devices()\nif devices:\n sdk.connect(devices[0])\n \n # Ensure platform is empty, then set zero point\n sdk.set_zero() # Tare the scale\n \n # Now ready for accurate measurements\n sdk.start_measurement(duration=10)\n \n # Wait for finish measurement\n sdk.wait()\n```\n\n**Notes**:\n- Device connection is required before calling this method\n- Call `connect()` first to establish device connection\n- Use this function when the scale platform is empty\n- This establishes the baseline for all subsequent weight measurements\n\n---\n\n### `set_scale(value)`\n\n**Description**: \nSets the scale calibration parameter of the device. This function adjusts the internal calibration value used by the device's weight measurement system to ensure accurate readings.\n\n**Parameters**:\n- `value` (int): Scale calibration parameter, must be within the range -32768 to 32767\n\n**Returns**: \nNone\n\n**Usage Example**:\n```python\n# Connect to device first\ndevices = sdk.scan_devices()\nif devices:\n sdk.connect(devices[0])\n \n # Set zero point first\n sdk.set_zero()\n \n # Set scale calibration parameter\n sdk.set_scale(12600) # Set calibration value to 12600\n \n # Start measurement with calibrated settings\n sdk.start_measurement(duration=10)\n sdk.wait()\n```\n\n**Notes**:\n- Device connection is required before calling this method\n- Call `connect()` first to establish device connection\n- Value must be an integer within the range -32768 to 32767\n- Use after `set_zero()` for optimal calibration\n- Consult device documentation for appropriate calibration values\n\n---\n\n## \ud83d\udd27 Device Calibration Workflow\n\nFor optimal measurement accuracy, follow this calibration sequence:\n\n```python\n# examples/calibration.py\nimport fisica_sdk as fisica\n\n# Initialize and connect\nsdk = fisica.FisicaSDK()\ndevices = sdk.scan_devices()\n\nif devices:\n sdk.connect(devices[0])\n \n # Step 1: Set zero point (empty platform)\n print(\"Please ensure platform is empty...\")\n input(\"Press Enter when ready...\")\n sdk.set_zero()\n print(\"Zero point calibrated.\")\n \n # Step 2: Set scale calibration parameter\n # Use appropriate calibration value for your device\n # (consult device documentation for recommended values)\n calibration_value = 12600 # Example value within -32768 to 32767 range\n sdk.set_scale(calibration_value)\n print(f\"Scale calibration set to: {calibration_value}\")\n \n # Step 3: Verify calibration with known reference weight\n print(\"Place a known reference weight on platform...\")\n reference_weight = float(input(\"Enter reference weight (kg): \"))\n \n # Take a measurement to verify accuracy\n sdk.start_measurement(duration=10)\n sdk.wait()\n report = sdk.analyze()\n measured_weight = report.weight\n \n print(f\"Reference: {reference_weight}kg, Measured: {measured_weight}kg\")\n print(f\"Accuracy: {abs(measured_weight - reference_weight):.3f}kg difference\")\n \n # Step 4: Ready for accurate measurements\n print(\"Calibration complete. Ready for measurements.\")\n sdk.disconnect()\n```\n\n---\n\n## \ud83e\udd1d Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## \ud83d\udcde Support\n\n- **Issues**: [GitHub Issues](https://github.com/care-co/fisica_sdk/issues)\n- **Email**: carencoinc@carenco.kr\n- **Website**: [https://carenco.kr](https://carenco.kr/en)\n\n## \ud83d\udd04 Changelog\n\n### v1.0.0 (Latest)\n- Initial release\n- Serial and Bluetooth device support\n- Real-time data visualization\n- Comprehensive pressure analysis\n\n---\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Care&Co Foot pressure analysis and visualization SDK",
"version": "1.0.1",
"project_urls": {
"Bug Tracker": "https://github.com/Care-Co/fisica_sdk/issues",
"Documentation": "https://fisica-sdk.readthedocs.io/",
"Homepage": "https://github.com/Care-Co/",
"Source Code": "https://github.com/Care-Co/fisica_sdk"
},
"split_keywords": [
"foot",
"pressure",
"analysis",
"medical",
"sensor",
"visualization"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "9ce626a8691b9f9effbe5a21fcbfdacc0c4fb8cb0e8aa2126fb369fb88038f2c",
"md5": "2216a9ea0a1d51981259fbbe7b841a2a",
"sha256": "baf27dff36a96272251cdcc148aa45024dbd9c559c1b9484b9934e828f486905"
},
"downloads": -1,
"filename": "fisica-1.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2216a9ea0a1d51981259fbbe7b841a2a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 43655,
"upload_time": "2025-10-21T03:29:07",
"upload_time_iso_8601": "2025-10-21T03:29:07.270282Z",
"url": "https://files.pythonhosted.org/packages/9c/e6/26a8691b9f9effbe5a21fcbfdacc0c4fb8cb0e8aa2126fb369fb88038f2c/fisica-1.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "1bb88c33774c8a2d39dccbe1bc4c8ff515f523a7ce708a135e83e136106b47bf",
"md5": "43f3c053fee68063f8ef3fa6fdb29f8c",
"sha256": "cb75e443d5fbeff3ef4cc22934778fa3eec8336436d0c07c501bcac94507bcfc"
},
"downloads": -1,
"filename": "fisica-1.0.1.tar.gz",
"has_sig": false,
"md5_digest": "43f3c053fee68063f8ef3fa6fdb29f8c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 40906,
"upload_time": "2025-10-21T03:29:08",
"upload_time_iso_8601": "2025-10-21T03:29:08.719657Z",
"url": "https://files.pythonhosted.org/packages/1b/b8/8c33774c8a2d39dccbe1bc4c8ff515f523a7ce708a135e83e136106b47bf/fisica-1.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-21 03:29:08",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Care-Co",
"github_project": "fisica_sdk",
"github_not_found": true,
"lcname": "fisica"
}