Skip to content

Commit d3b933e

Browse files
authored
Merge pull request #112 from AllenNeuralDynamics/wip/pytest
Wip/pytest
2 parents 01bc137 + 6f6b2b3 commit d3b933e

15 files changed

+853
-10
lines changed

Diff for: parallax/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import os
66

7-
__version__ = "1.1.2"
7+
__version__ = "1.1.3"
88

99
# allow multiple OpenMP instances
1010
os.environ["KMP_DUPLICATE_LIB_OK"] = "True"

Diff for: tests/test_curr_bg_cmp_processor.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import pytest
2-
import numpy as np
32
import cv2
43
import os
54
from parallax.curr_bg_cmp_processor import CurrBgCmpProcessor
@@ -21,7 +20,7 @@ def load_images_from_folder(folder):
2120
images.append(img)
2221
return images
2322

24-
@pytest.fixture(scope="session")
23+
@pytest.fixture(scope="function")
2524
def sample_images():
2625
"""Fixture to provide a way to process images into `curr_img`, `mask`."""
2726
def process_image(org_img, mask_generator):
@@ -109,7 +108,6 @@ def test_update_cmp(setup_curr_bg_cmp_processor, sample_images):
109108
# Simulate the next frame (using the same or next image in the sequence)
110109
ret, precise_tip = processor.update_cmp(curr_img, mask, org_img)
111110

112-
print(f"Frame {i}: Precise tip found: {precise_tip}, tip: {tip}")
113111
if precise_tip:
114112
tip = processor.ProbeDetector.probe_tip
115113
print(f"Frame {i}: Precise tip found: {precise_tip}, tip: {tip}")

Diff for: tests/test_curr_prev_cmp_processor.py

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import pytest
2+
import cv2
3+
import os
4+
from parallax.curr_prev_cmp_processor import CurrPrevCmpProcessor
5+
from parallax.mask_generator import MaskGenerator
6+
from parallax.probe_detector import ProbeDetector
7+
8+
# Define the folder containing your test images
9+
IMG_SIZE = (1000, 750)
10+
IMG_SIZE_ORIGINAL = (4000, 3000)
11+
12+
# Helper function to load images from a folder
13+
def load_images_from_folder(folder):
14+
"""Load and sort images from a specified folder."""
15+
images = []
16+
for filename in sorted(os.listdir(folder)):
17+
img_path = os.path.join(folder, filename)
18+
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
19+
if img is not None:
20+
images.append(img)
21+
return images
22+
23+
@pytest.fixture
24+
def setup_curr_prev_cmp_processor():
25+
"""Fixture to set up an instance of CurrBgCmpProcessor."""
26+
cam_name = "MockCam"
27+
probeDetector = ProbeDetector(cam_name, (1000, 750))
28+
29+
processor = CurrPrevCmpProcessor(
30+
cam_name=cam_name,
31+
ProbeDetector=probeDetector,
32+
original_size=IMG_SIZE_ORIGINAL,
33+
resized_size=IMG_SIZE,
34+
)
35+
return processor
36+
37+
@pytest.fixture(scope="function")
38+
def sample_images():
39+
"""Fixture to provide a way to process images into `curr_img`, `mask`."""
40+
def process_image(org_img, mask_generator):
41+
"""Resize, blur, and generate mask for a given original image."""
42+
resized_img = cv2.resize(org_img, IMG_SIZE)
43+
curr_img = cv2.GaussianBlur(resized_img, (9, 9), 0)
44+
mask = mask_generator.process(resized_img)
45+
return curr_img, mask
46+
47+
return process_image
48+
49+
# Tests
50+
def test_first_cmp(setup_curr_prev_cmp_processor, sample_images):
51+
"""Test the first_cmp method with multiple images."""
52+
processor = setup_curr_prev_cmp_processor
53+
54+
# Initialize the mask generator
55+
mask_generator = MaskGenerator() # Replace with appropriate constructor
56+
57+
# Load test images from the folder
58+
base_dir = "tests/test_data/probe_detect_manager"
59+
images = load_images_from_folder(base_dir)
60+
61+
ret, precise_tip, tip, prev_img = False, False, None, None
62+
# Iterate over each frame and process it
63+
for i, org_img in enumerate(images):
64+
# Generate `curr_img` and `mask` for each frame using the `sample_images` fixture
65+
curr_img, mask = sample_images(org_img, mask_generator)
66+
if prev_img is None:
67+
prev_img = curr_img
68+
continue
69+
70+
# Call the method to test for each frame
71+
ret, precise_tip = processor.first_cmp(curr_img, prev_img, mask, org_img)
72+
73+
if precise_tip:
74+
tip = processor.ProbeDetector.probe_tip
75+
print(f"Frame {i}: Precise tip found: {precise_tip}, tip: {tip}")
76+
break
77+
78+
# Perform assertions
79+
assert ret is not False, f"Return value of ret should not be None."
80+
assert precise_tip is not False, f"Precise_tip should be detected."
81+
assert isinstance(tip, tuple), "The tip should be a tuple."
82+
assert len(tip) == 2, "The tip should contain two elements (x, y)."
83+
84+
85+
def test_update_cmp(setup_curr_prev_cmp_processor, sample_images):
86+
"""Test the update_cmp method with multiple images."""
87+
processor = setup_curr_prev_cmp_processor
88+
89+
# Initialize the mask generator
90+
mask_generator = MaskGenerator() # Replace with appropriate constructor
91+
92+
# Load test images from the folder
93+
base_dir = "tests/test_data/probe_detect_manager"
94+
images = load_images_from_folder(base_dir)
95+
96+
is_first_detect = True
97+
ret, precise_tip, tip, prev_img = False, False, None, None
98+
# Iterate over each frame and process it
99+
for i, org_img in enumerate(images):
100+
# Generate `curr_img` and `mask` for each frame using the `sample_images` fixture
101+
curr_img, mask = sample_images(org_img, mask_generator)
102+
if prev_img is None:
103+
prev_img = curr_img
104+
continue
105+
106+
# Call the first_cmp method to set the initial state
107+
if is_first_detect:
108+
ret_, _ = processor.first_cmp(curr_img, prev_img, mask, org_img)
109+
if ret_:
110+
is_first_detect = False
111+
continue
112+
113+
# Simulate the next frame (using the same or next image in the sequence)
114+
ret, precise_tip = processor.update_cmp(curr_img, prev_img, mask, org_img)
115+
116+
if precise_tip:
117+
tip = processor.ProbeDetector.probe_tip
118+
print(f"Frame {i}: Precise tip found: {precise_tip}, tip: {tip}")
119+
break
120+
121+
# Perform assertions
122+
assert ret is not False, f"Return value of ret should not be None."
123+
assert precise_tip is not False, f"Precise_tip should be detected."
124+
assert isinstance(tip, tuple), "The tip should be a tuple."
125+
assert len(tip) == 2, "The tip should contain two elements (x, y)."

Diff for: tests/test_data/probe_fine_tip_detector/tip.jpg

1.92 KB
Loading

Diff for: tests/test_point_mesh.py

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import pytest
2+
import os
3+
import numpy as np
4+
from PyQt5.QtWidgets import QApplication
5+
from parallax.point_mesh import PointMesh
6+
from parallax.model import Model # Replace with the actual class that represents the model
7+
8+
@pytest.fixture(scope="session")
9+
def qapp():
10+
"""
11+
Fixture to create a QApplication instance.
12+
"""
13+
app = QApplication([])
14+
yield app
15+
app.quit()
16+
17+
@pytest.fixture
18+
def transformation_data():
19+
"""
20+
Fixture for providing transformation matrix data.
21+
"""
22+
transM = np.array([
23+
[0.922764124, 0.151342504, 0.354403467, -619.603148],
24+
[0.039239136, 0.877976151, -0.477093459, 32.53554828],
25+
[-0.383362311, 0.454151213, 0.804226345, 15143.53139],
26+
[0, 0, 0, 1]
27+
])
28+
scale = np.array([0.990795167, -0.978485802, -1.004634464])
29+
return transM, scale
30+
31+
@pytest.fixture
32+
def sample_csv_file():
33+
"""
34+
Fixture to provide the path to the existing CSV file.
35+
"""
36+
csv_path = os.path.join("tests", "test_data", "point_mesh", "points.csv")
37+
return csv_path
38+
39+
@pytest.fixture
40+
def point_mesh_widget(qapp, transformation_data, sample_csv_file):
41+
"""
42+
Fixture to create a PointMesh widget with sample data.
43+
"""
44+
model = Model() # Replace with actual model initialization
45+
transM, scale = transformation_data
46+
widget = PointMesh(model, sample_csv_file, "SN46115", transM, scale)
47+
return widget
48+
49+
def test_parse_csv(point_mesh_widget):
50+
"""
51+
Test that the CSV file is parsed correctly and points are loaded.
52+
"""
53+
point_mesh_widget._parse_csv()
54+
55+
# Verify that local points are parsed and transformed to global coordinates
56+
assert len(point_mesh_widget.local_pts) > 0, "Local points should be loaded"
57+
assert len(point_mesh_widget.global_pts) > 0, "Global points should be loaded"
58+
59+
# Iterate over all rows in the local and global points and test them
60+
for i, (local_point, global_point) in enumerate(zip(point_mesh_widget.local_pts, point_mesh_widget.global_pts)):
61+
print(f"Row {i}: local_transform: {local_point}, global: {global_point}")
62+
63+
# Use np.testing.assert_allclose with an absolute tolerance of 50
64+
np.testing.assert_allclose(
65+
local_point, global_point, atol=50,
66+
err_msg=f"Row {i}: The transformed local point does not match the expected global point within a tolerance of 50"
67+
)
68+
69+
def test_draw_specific_points(point_mesh_widget):
70+
"""
71+
Test that specific points are drawn correctly.
72+
"""
73+
point_mesh_widget._parse_csv()
74+
point_mesh_widget._draw_specific_points("local_pts")
75+
76+
# Check if the local points trace is added
77+
assert "local_pts" in point_mesh_widget.traces, "Trace for local points should be added"
78+
79+
# Verify that the trace contains data points
80+
trace = point_mesh_widget.traces["local_pts"]
81+
assert len(trace.x) > 0, "Trace should contain X values"
82+
assert len(trace.y) > 0, "Trace should contain Y values"
83+
assert len(trace.z) > 0, "Trace should contain Z values"
84+
85+
# Draw global points
86+
point_mesh_widget._draw_specific_points("global_pts")
87+
88+
# Check if the global points trace is added
89+
assert "global_pts" in point_mesh_widget.traces, "Trace for global points should be added"
90+
91+
# Verify that the trace contains data points for global points
92+
global_trace = point_mesh_widget.traces["global_pts"]
93+
assert len(global_trace.x) > 0, "Global trace should contain X values"
94+
assert len(global_trace.y) > 0, "Global trace should contain Y values"
95+
assert len(global_trace.z) > 0, "Global trace should contain Z values"

Diff for: tests/test_probe_calibration.py

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import pytest
2+
import os
3+
import pandas as pd
4+
import numpy as np
5+
from PyQt5.QtCore import QObject, pyqtSignal
6+
from parallax.probe_calibration import ProbeCalibration
7+
from parallax.model import Model # Replace with the actual class that represents the model
8+
9+
10+
@pytest.fixture
11+
def sample_csv_file():
12+
"""
13+
Fixture to provide the path to the existing CSV file.
14+
"""
15+
# Path to the points.csv file in the test_data directory
16+
csv_path = os.path.join("tests", "test_data", "probe_calibration", "points.csv")
17+
return csv_path
18+
19+
@pytest.fixture
20+
def model():
21+
"""
22+
Fixture for creating a model object.
23+
"""
24+
return Model()
25+
26+
@pytest.fixture
27+
def stage_listener():
28+
"""
29+
Fixture for creating a mock stage listener.
30+
"""
31+
class MockStageListener(QObject):
32+
probeCalibRequest = pyqtSignal()
33+
34+
return MockStageListener()
35+
36+
@pytest.fixture
37+
def probe_calibration(model, stage_listener):
38+
"""
39+
Fixture for creating a ProbeCalibration instance.
40+
"""
41+
return ProbeCalibration(model, stage_listener)
42+
43+
def test_probe_calibration_update(probe_calibration, sample_csv_file):
44+
"""
45+
Test the update method of ProbeCalibration with each row from the CSV file.
46+
"""
47+
# Load the CSV data
48+
df = pd.read_csv(sample_csv_file)
49+
50+
# Iterate over each row in the CSV file
51+
for idx, row in df.iterrows():
52+
# Create a mock stage object with values from the CSV row
53+
class MockStage:
54+
def __init__(self, row):
55+
self.sn = row["sn"]
56+
self.stage_x = row["local_x"]
57+
self.stage_y = row["local_y"]
58+
self.stage_z = row["local_z"]
59+
self.stage_x_global = row["global_x"]
60+
self.stage_y_global = row["global_y"]
61+
self.stage_z_global = row["global_z"]
62+
63+
stage = MockStage(row)
64+
65+
probe_calibration.update(stage)
66+
67+
if probe_calibration._is_enough_points():
68+
break
69+
70+
# Perform assertions to ensure that the calibration is being updated correctly.
71+
assert probe_calibration.transM_LR is not None, f"Transformation matrix should be set for stage {stage.sn}"
72+
assert probe_calibration.scale is not None, f"Scale should be set for stage {stage.sn}"
73+
74+
# Print out the transformation matrix and scale for verification
75+
print(f"Test row {idx}: SN = {stage.sn}")
76+
print(f"Transformation matrix:\n{probe_calibration.transM_LR}")
77+
print(f"Scale: {probe_calibration.scale}")
78+
print(f"Average Error: {probe_calibration.avg_err}")
79+
80+
assert probe_calibration._is_criteria_avg_error_threshold(), \
81+
f"Average error should meet threshold for row {idx}, SN = {stage.sn}"
82+

Diff for: tests/test_probe_detect_manager.py

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import cv2
33
import os
44
import time
5-
from PyQt5.QtCore import QCoreApplication, QEventLoop
65
from parallax.probe_detect_manager import ProbeDetectManager
76

87
# Define the folder containing your test images

0 commit comments

Comments
 (0)