Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions backend/python/acquisition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import numpy as np

class AcquisitionModule:
def __init__(self, rgb_shape=(480, 640, 3), depth_shape=(480, 640)):
self.rgb_shape = rgb_shape
self.depth_shape = depth_shape

def get_frames(self):
"""
Simulates fetching RGB and depth frames and saves them to .npy files.
Returns the filepaths of the saved frames.
"""
rgb_frame = np.random.randint(0, 256, size=self.rgb_shape, dtype=np.uint8)
depth_frame = np.random.rand(*self.depth_shape).astype(np.float32) * 1000 # Simulate depth in mm

rgb_filepath = "temp_rgb.npy"
depth_filepath = "temp_depth.npy"

np.save(rgb_filepath, rgb_frame)
np.save(depth_filepath, depth_frame)

return rgb_filepath, depth_filepath

if __name__ == '__main__':
# Example usage
acquisition_module = AcquisitionModule()
rgb_path, depth_path = acquisition_module.get_frames()
print("RGB frame saved to:", rgb_path)
print("Depth frame saved to:", depth_path)

# Verify by loading
loaded_rgb = np.load(rgb_path)
loaded_depth = np.load(depth_path)
print("Loaded RGB Frame Shape:", loaded_rgb.shape)
print("Loaded Depth Frame Shape:", loaded_depth.shape)
110 changes: 110 additions & 0 deletions backend/python/data_management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import json
import os

class PatientDataManager:
def __init__(self, data_file_path="patient_data.json"):
self.data_file_path = data_file_path
self.patient_data = {}
self.load_data()

def add_patient_record(self, patient_id, patient_name, scan_data_reference):
"""Adds a new patient record."""
if patient_id in self.patient_data:
print(f"Warning: Patient ID {patient_id} already exists. Overwriting.")
self.patient_data[patient_id] = {
"name": patient_name,
"scan_data_reference": scan_data_reference
}
print(f"Record added for patient ID {patient_id}.")

def get_patient_record(self, patient_id):
"""Retrieves a patient record by patient_id."""
return self.patient_data.get(patient_id, None)

def save_data(self):
"""Saves the current patient data to the JSON file."""
try:
with open(self.data_file_path, 'w') as f:
json.dump(self.patient_data, f, indent=4)
print(f"Data saved to {self.data_file_path}")
except IOError as e:
print(f"Error saving data to {self.data_file_path}: {e}")

def load_data(self):
"""Loads patient data from the JSON file if it exists."""
if not os.path.exists(self.data_file_path):
print(f"Data file {self.data_file_path} not found. Starting with empty data.")
self.patient_data = {}
return

try:
with open(self.data_file_path, 'r') as f:
self.patient_data = json.load(f)
print(f"Data loaded from {self.data_file_path}")
except IOError as e:
print(f"Error loading data from {self.data_file_path}: {e}. Starting with empty data.")
self.patient_data = {}
except json.JSONDecodeError as e:
print(f"Error decoding JSON from {self.data_file_path}: {e}. Starting with empty data.")
self.patient_data = {}

if __name__ == "__main__":
print("Starting PatientDataManager Test Suite...")
test_file = "test_patient_data.json"

# Clean up before test, if file exists from a previous failed run
if os.path.exists(test_file):
os.remove(test_file)

# 1. Create an instance of PatientDataManager
print("\nStep 1: Creating first PatientDataManager instance...")
manager1 = PatientDataManager(data_file_path=test_file)

# 2. Add a few patient records
print("\nStep 2: Adding patient records...")
manager1.add_patient_record("P001", "John Doe", "scan_ref_001.stl")
manager1.add_patient_record("P002", "Jane Smith", "scan_ref_002.stl")
manager1.add_patient_record("P003", "Alice Brown", "/path/to/scan_003.npy")

# Verify records in manager1
print("\nVerifying records in manager1:")
print("Record P001:", manager1.get_patient_record("P001"))
print("Record P002:", manager1.get_patient_record("P002"))

# 3. Save the data
print("\nStep 3: Saving data...")
manager1.save_data()

# 4. Create another instance of PatientDataManager (simulating loading from the file)
print("\nStep 4: Creating second PatientDataManager instance to load data...")
manager2 = PatientDataManager(data_file_path=test_file)

# 5. Retrieve and print the records to verify they were saved and loaded correctly
print("\nStep 5: Retrieving and verifying records from manager2...")
record_p001_loaded = manager2.get_patient_record("P001")
record_p002_loaded = manager2.get_patient_record("P002")
record_p003_loaded = manager2.get_patient_record("P003")

print("Loaded Record P001:", record_p001_loaded)
print("Loaded Record P002:", record_p002_loaded)
print("Loaded Record P003:", record_p003_loaded)

assert record_p001_loaded is not None and record_p001_loaded["name"] == "John Doe"
assert record_p002_loaded is not None and record_p002_loaded["scan_data_reference"] == "scan_ref_002.stl"
assert record_p003_loaded is not None and record_p003_loaded["name"] == "Alice Brown"

# Test getting a non-existent record
print("\nTesting retrieval of a non-existent record (P004):")
non_existent_record = manager2.get_patient_record("P004")
print("Record P004:", non_existent_record)
assert non_existent_record is None

# 6. Clean up by removing the test_patient_data.json file
print("\nStep 6: Cleaning up test file...")
if os.path.exists(test_file):
os.remove(test_file)
print(f"Removed test file: {test_file}")
else:
print(f"Test file {test_file} not found for cleanup.")

print("\nPatientDataManager Test Suite Completed Successfully!")
1 change: 1 addition & 0 deletions backend/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
numpy
11 changes: 11 additions & 0 deletions backend/rust/mock_rust_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sys

if __name__ == "__main__":
if len(sys.argv) == 3:
rgb_path = sys.argv[1]
depth_path = sys.argv[2]
# Simulate the Rust executable's output format
print(f"Processed 3D model from {rgb_path} and {depth_path}")
else:
print("Usage: python mock_rust_runner.py <rgb_path> <depth_path>", file=sys.stderr)
sys.exit(1)
7 changes: 7 additions & 0 deletions backend/rust/reconstruction_engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions backend/rust/reconstruction_engine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "reconstruction_engine"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
70 changes: 70 additions & 0 deletions backend/rust/reconstruction_engine/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
pub fn reconstruct_from_frames(rgb_path: &str, depth_path: &str) -> String {
// Simulate reading and processing the frame data from the paths.
println!("Reading RGB frame from path: {}", rgb_path);
println!("Reading depth frame from path: {}", depth_path);

// Simulate some complex 3D reconstruction logic using the data from paths
// In a real application, you would use libraries to load .npy files or other image formats.
// For this mock, we just acknowledge the paths.

format!("Processed 3D model from {} and {}", rgb_path, depth_path)
}

pub fn simplify_mesh(mesh_data: &str) -> String {
// In a real scenario, this function would perform mesh simplification algorithms.
// For this mock, we'll just prepend "Simplified ".
format!("Simplified {}", mesh_data)
}

pub fn smooth_mesh(mesh_data: &str) -> String {
// In a real scenario, this function would perform mesh smoothing algorithms.
// For this mock, we'll just prepend "Smoothed ".
format!("Smoothed {}", mesh_data)
}

pub fn export_to_stl(mesh_data: &str, filepath: &str) -> String {
// In a real scenario, this function would handle the STL file creation and writing.
// For this mock, we simulate the confirmation message.
format!("Exported {} to {}.stl", mesh_data, filepath)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}

#[test]
fn test_reconstruct_from_frames() {
let rgb_path = "dummy_rgb.png";
let depth_path = "dummy_depth.png";
let expected_output = format!("Processed 3D model from {} and {}", rgb_path, depth_path);
assert_eq!(reconstruct_from_frames(rgb_path, depth_path), expected_output);
}

#[test]
fn test_simplify_mesh() {
let mesh_data = "raw_model_data";
let expected_output = "Simplified raw_model_data";
assert_eq!(simplify_mesh(mesh_data), expected_output);
}

#[test]
fn test_smooth_mesh() {
let mesh_data = "simplified_model_data";
let expected_output = "Smoothed simplified_model_data";
assert_eq!(smooth_mesh(mesh_data), expected_output);
}

#[test]
fn test_export_to_stl() {
let mesh_data = "final_model_data";
let filepath = "/path/to/model_output";
let expected_output = "Exported final_model_data to /path/to/model_output.stl";
assert_eq!(export_to_stl(mesh_data, filepath), expected_output);
}
}
1 change: 1 addition & 0 deletions backend/rust/reconstruction_engine/target/.rustc_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"rustc_fingerprint":7337126974762140843,"outputs":{"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/usr\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.75.0 (82e1608df 2023-12-21) (built from a source tarball)\nbinary: rustc\ncommit-hash: 82e1608dfa6e0b5569232559e3d385fea5a93112\ncommit-date: 2023-12-21\nhost: x86_64-unknown-linux-gnu\nrelease: 1.75.0\nLLVM version: 17.0.6\n","stderr":""}},"successes":{}}
3 changes: 3 additions & 0 deletions backend/rust/reconstruction_engine/target/CACHEDIR.TAG
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/
Empty file.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file has an mtime of when this was started.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00b467c6ee7f0562
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"rustc":12047664292709028906,"features":"[]","target":6295730984014464461,"profile":15632368228915330634,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/reconstruction_engine-da6ae83cfbdab3ea/dep-test-lib-reconstruction_engine"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file has an mtime of when this was started.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
daf3f54bb5ff0f4f
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"rustc":12047664292709028906,"features":"[]","target":6295730984014464461,"profile":11597332650809196192,"path":17523903030608720598,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/reconstruction_engine-e0d1765e110eb1e5/dep-lib-reconstruction_engine"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/app/backend/rust/reconstruction_engine/target/debug/deps/reconstruction_engine-da6ae83cfbdab3ea: src/lib.rs

/app/backend/rust/reconstruction_engine/target/debug/deps/reconstruction_engine-da6ae83cfbdab3ea.d: src/lib.rs

src/lib.rs:
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/app/backend/rust/reconstruction_engine/target/debug/deps/libreconstruction_engine-e0d1765e110eb1e5.rmeta: src/lib.rs

/app/backend/rust/reconstruction_engine/target/debug/deps/libreconstruction_engine-e0d1765e110eb1e5.rlib: src/lib.rs

/app/backend/rust/reconstruction_engine/target/debug/deps/reconstruction_engine-e0d1765e110eb1e5.d: src/lib.rs

src/lib.rs:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
87 changes: 87 additions & 0 deletions frontend/main_ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import sys
import subprocess
import os # For path joining
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QWidget, QVBoxLayout, QPushButton
from PyQt6.QtCore import Qt

# Adjust Python path to find backend modules
# This assumes the script is run from the root of the repository or that the structure is in PYTHONPATH
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from backend.python.acquisition import AcquisitionModule


class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("3D Reconstruction Viewer")
self.setGeometry(100, 100, 800, 600) # x, y, width, height

# Central widget and layout
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)

# Placeholder for 3D view
self.placeholder_label = QLabel("3D View Placeholder", self)
self.placeholder_label.setStyleSheet("border: 1px solid black; padding: 10px; font-size: 16px;")
self.placeholder_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.placeholder_label)

# Start Scan button
self.start_scan_button = QPushButton("Start Scan", self)
self.start_scan_button.clicked.connect(self.on_start_scan_clicked)
layout.addWidget(self.start_scan_button)

central_widget.setLayout(layout)

# Initialize Acquisition Module
self.acquisition_module = AcquisitionModule()
# Define path to the mock rust runner - adjust if your structure is different
# Assuming 'frontend' and 'backend' are sibling directories
self.mock_rust_runner_path = os.path.join(
os.path.dirname(__file__), # current dir (frontend)
"..", # project root
"backend", "rust", "mock_rust_runner.py"
)


def on_start_scan_clicked(self):
self.placeholder_label.setText("Scanning...")
QApplication.processEvents() # Update UI

try:
# 1. Get frame paths from acquisition module
rgb_filepath, depth_filepath = self.acquisition_module.get_frames()
self.placeholder_label.setText(f"Frames saved: {rgb_filepath}, {depth_filepath}\nRunning reconstruction...")
QApplication.processEvents()

# 2. Simulate calling Rust reconstruction engine
# In a real scenario, this would be the path to the compiled Rust executable
cmd = [sys.executable, self.mock_rust_runner_path, rgb_filepath, depth_filepath]

# Run the command
# We are in frontend/ folder, paths temp_rgb.npy and temp_depth.npy are in current working directory
# which is the root of the repo if we run from root.
# The acquisition module saves them to CWD.
# The mock_rust_runner.py expects them to be accessible.
process = subprocess.run(cmd, capture_output=True, text=True, check=True, cwd=".") # Run from repo root

result_output = process.stdout.strip()
self.placeholder_label.setText(result_output)

except FileNotFoundError:
self.placeholder_label.setText(f"Error: Mock runner script not found at {self.mock_rust_runner_path}")
except subprocess.CalledProcessError as e:
self.placeholder_label.setText(f"Error during reconstruction: {e.stderr}")
except Exception as e:
self.placeholder_label.setText(f"An unexpected error occurred: {str(e)}")


def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions frontend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PyQt6