Skip to content

Commit 6d23365

Browse files
committed
Merge branch 'master' of https://github.com/nanophys/MeasureIt
2 parents 33b7e10 + cef5104 commit 6d23365

File tree

4 files changed

+245
-17
lines changed

4 files changed

+245
-17
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ where = ["src"]
1010

1111
[project]
1212
name = "qmeasure"
13-
version = "1.2.3"
13+
version = "1.2.4"
1414
description = "Measurement code for condensed matter groups at UW based on QCoDeS."
1515
readme = {file = "README.md", content-type = "text/markdown"}
1616
license = "GPL-3.0-or-later"

src/measureit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
try:
4949
__version__ = metadata.version("qmeasure")
5050
except metadata.PackageNotFoundError: # pragma: no cover - dev installs
51-
__version__ = "1.2.3"
51+
__version__ = "1.2.4"
5252

5353

5454
# Display data directory info on first import (only in interactive sessions)

src/measureit/visualization/heatmap_thread.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,10 @@ def handle_key_press(self, event):
135135
"""Handle keyboard shortcuts for sweep control from heatmap window."""
136136
try:
137137
key = event.key()
138-
if key == pg.QtCore.Qt.Key_Escape:
139-
# Pause the 2D sweep gracefully
140-
try:
141-
self.sweep.pause()
138+
if key == pg.QtCore.Qt.Key_Escape:
139+
# Pause the 2D sweep gracefully
140+
try:
141+
self.sweep.pause()
142142
except Exception:
143143
pass
144144
elif key == pg.QtCore.Qt.Key_Return or key == pg.QtCore.Qt.Key_Enter:
@@ -246,8 +246,8 @@ def create_figs(self):
246246
)
247247
main_layout.addWidget(self.info_label)
248248

249-
state = getattr(self.sweep, "progressState", None)
250-
if state is not None and getattr(state, "progress", None) is not None:
249+
state = getattr(self.sweep, "progressState", None)
250+
if state is not None and getattr(state, "progress", None) is not None:
251251
progress_info = QHBoxLayout()
252252
progress_info.setContentsMargins(0, 0, 0, 0)
253253
progress_info.setSpacing(8)
@@ -506,7 +506,7 @@ def add_to_heatmap(self, data_dict):
506506
self.param_surfaces[param_index]["min"] = y_val
507507

508508
# Update the data array row
509-
row_idx = self.res_out - out_index - 1 # Flip for proper orientation
509+
row_idx = out_index
510510
for i, in_key in enumerate(self.in_keys):
511511
if i < self.res_in:
512512
self.param_surfaces[param_index]["data"][row_idx][i] = (
@@ -794,14 +794,14 @@ def _update_progress_widgets(self):
794794
if state is None:
795795
return
796796

797-
progress = getattr(state, "progress", None)
798-
if progress is None:
799-
self.progress_bar.setFormat("Progress: --")
800-
self.progress_bar.setValue(0)
801-
else:
802-
self.progress_bar.setFormat("Progress: %p%")
803-
progress_value = int(max(0.0, min(1.0, progress)) * 1000)
804-
self.progress_bar.setValue(progress_value)
797+
progress = getattr(state, "progress", None)
798+
if progress is None:
799+
self.progress_bar.setFormat("Progress: --")
800+
self.progress_bar.setValue(0)
801+
else:
802+
self.progress_bar.setFormat("Progress: %p%")
803+
progress_value = int(max(0.0, min(1.0, progress)) * 1000)
804+
self.progress_bar.setValue(progress_value)
805805

806806
def _format_seconds(value):
807807
if value is None:
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
"""Unit tests for Heatmap axis orientation in Sweep2D.
2+
3+
This test verifies that the x and y axes in the heatmap correctly correspond
4+
to the inner and outer sweep parameters respectively.
5+
"""
6+
7+
import numpy as np
8+
import pytest
9+
10+
11+
class TestHeatmapAxisOrientation:
12+
"""Test that heatmap data is correctly oriented with respect to sweep axes."""
13+
14+
def test_data_row_corresponds_to_outer_sweep_value(self, mock_parameters, fast_sweep_kwargs, qapp):
15+
"""Test that data for a given outer sweep value is placed in the correct row.
16+
17+
The heatmap should display:
18+
- X axis (horizontal): inner sweep parameter values
19+
- Y axis (vertical): outer sweep parameter values
20+
21+
When data is collected at out_value = out_start (first outer step),
22+
it should appear at the bottom of the heatmap (row 0 in the data array
23+
when using PyQtGraph's default coordinate system with setRect).
24+
25+
Bug: Currently the row index is inverted, causing out_start data to
26+
appear at out_stop position and vice versa.
27+
"""
28+
from measureit.sweep.sweep2d import Sweep2D
29+
from measureit.visualization.heatmap_thread import Heatmap
30+
31+
# Create a sweep with known parameters
32+
# Inner sweep: voltage from 0 to 1 with step 0.5 (3 points: 0, 0.5, 1)
33+
# Outer sweep: gate from -1 to 1 with step 1.0 (3 points: -1, 0, 1)
34+
in_params = [mock_parameters["voltage"], 0, 1, 0.5]
35+
out_params = [mock_parameters["gate"], -1, 1, 1.0]
36+
37+
sweep = Sweep2D(
38+
in_params,
39+
out_params,
40+
outer_delay=0.1,
41+
**fast_sweep_kwargs,
42+
)
43+
sweep.follow_param(mock_parameters["current"])
44+
45+
# Create heatmap and initialize its data structures
46+
heatmap = Heatmap(sweep)
47+
48+
# Manually initialize the heatmap data structures (simulating create_figs)
49+
heatmap.res_in = 3 # 0, 0.5, 1
50+
heatmap.res_out = 3 # -1, 0, 1
51+
heatmap.in_keys = [0.0, 0.5, 1.0]
52+
heatmap.out_keys = [-1.0, 0.0, 1.0]
53+
heatmap.in_step = 0.5
54+
heatmap.out_step = 1.0
55+
heatmap.figs_set = True
56+
heatmap.param_surfaces = {}
57+
58+
# Create a data dict to simulate data from the plotter
59+
# This represents completing an inner sweep at outer value = -1.0 (out_start)
60+
# The measurement values are [10, 20, 30] at inner values [0, 0.5, 1.0]
61+
data_dict = {
62+
"forward": (
63+
np.array([0.0, 0.5, 1.0]), # x_data (inner sweep values)
64+
np.array([10.0, 20.0, 30.0]), # y_data (measured values)
65+
),
66+
"param_index": 1, # Index of the parameter being measured
67+
"out_value": -1.0, # This is out_start
68+
}
69+
70+
# Add the data to the heatmap
71+
heatmap.add_to_heatmap(data_dict)
72+
73+
# Get the data array for this parameter
74+
data_array = heatmap.param_surfaces[1]["data"]
75+
76+
# The data array has shape (res_out, res_in) = (3, 3)
77+
# Rows correspond to outer sweep values, columns to inner sweep values
78+
#
79+
# With setRect mapping:
80+
# - Row 0 should correspond to out_start = -1.0 (bottom of y-axis)
81+
# - Row 2 should correspond to out_stop = 1.0 (top of y-axis)
82+
#
83+
# So data collected at out_value = -1.0 should be in row 0.
84+
85+
# Verify the data is in row 0 (corresponding to out_start = -1.0)
86+
# This is where the bug manifests: currently the data ends up in row 2
87+
expected_row = 0 # out_start should map to row 0
88+
89+
# Check that row 0 contains the data we added
90+
assert data_array[expected_row, 0] == 10.0, \
91+
f"Data at (row=0, col=0) should be 10.0 but got {data_array[expected_row, 0]}. " \
92+
f"Row 0 should contain data for out_value=-1.0 (out_start)."
93+
assert data_array[expected_row, 1] == 20.0, \
94+
f"Data at (row=0, col=1) should be 20.0 but got {data_array[expected_row, 1]}."
95+
assert data_array[expected_row, 2] == 30.0, \
96+
f"Data at (row=0, col=2) should be 30.0 but got {data_array[expected_row, 2]}."
97+
98+
# Also verify that other rows are still zero (data should NOT be in row 2)
99+
assert data_array[2, 0] == 0.0, \
100+
f"Row 2 should be empty but contains {data_array[2, 0]}. " \
101+
f"Bug: data for out_start is incorrectly placed at out_stop row."
102+
103+
def test_multiple_outer_steps_correct_row_mapping(self, mock_parameters, fast_sweep_kwargs, qapp):
104+
"""Test that multiple outer sweep steps map to correct rows.
105+
106+
For a sweep with outer values [-1, 0, 1]:
107+
- out_value = -1 (out_start) should map to row 0 (bottom)
108+
- out_value = 0 should map to row 1 (middle)
109+
- out_value = 1 (out_stop) should map to row 2 (top)
110+
"""
111+
from measureit.sweep.sweep2d import Sweep2D
112+
from measureit.visualization.heatmap_thread import Heatmap
113+
114+
in_params = [mock_parameters["voltage"], 0, 1, 0.5]
115+
out_params = [mock_parameters["gate"], -1, 1, 1.0]
116+
117+
sweep = Sweep2D(
118+
in_params,
119+
out_params,
120+
outer_delay=0.1,
121+
**fast_sweep_kwargs,
122+
)
123+
sweep.follow_param(mock_parameters["current"])
124+
125+
heatmap = Heatmap(sweep)
126+
heatmap.res_in = 3
127+
heatmap.res_out = 3
128+
heatmap.in_keys = [0.0, 0.5, 1.0]
129+
heatmap.out_keys = [-1.0, 0.0, 1.0]
130+
heatmap.in_step = 0.5
131+
heatmap.out_step = 1.0
132+
heatmap.figs_set = True
133+
heatmap.param_surfaces = {}
134+
135+
# Add data for each outer sweep step with distinguishable values
136+
test_cases = [
137+
{"out_value": -1.0, "values": [100.0, 110.0, 120.0], "expected_row": 0},
138+
{"out_value": 0.0, "values": [200.0, 210.0, 220.0], "expected_row": 1},
139+
{"out_value": 1.0, "values": [300.0, 310.0, 320.0], "expected_row": 2},
140+
]
141+
142+
for case in test_cases:
143+
data_dict = {
144+
"forward": (
145+
np.array([0.0, 0.5, 1.0]),
146+
np.array(case["values"]),
147+
),
148+
"param_index": 1,
149+
"out_value": case["out_value"],
150+
}
151+
heatmap.add_to_heatmap(data_dict)
152+
153+
data_array = heatmap.param_surfaces[1]["data"]
154+
155+
# Verify each row contains the correct data
156+
for case in test_cases:
157+
row = case["expected_row"]
158+
expected_values = case["values"]
159+
actual_values = [data_array[row, 0], data_array[row, 1], data_array[row, 2]]
160+
161+
assert actual_values == expected_values, \
162+
f"Row {row} (out_value={case['out_value']}) should contain {expected_values} " \
163+
f"but got {actual_values}. The y-axis mapping is inverted."
164+
165+
def test_inner_sweep_maps_to_columns(self, mock_parameters, fast_sweep_kwargs, qapp):
166+
"""Test that inner sweep values correctly map to columns (x-axis).
167+
168+
For inner sweep values [0, 0.5, 1.0]:
169+
- in_value = 0 should map to column 0 (left)
170+
- in_value = 0.5 should map to column 1 (middle)
171+
- in_value = 1.0 should map to column 2 (right)
172+
"""
173+
from measureit.sweep.sweep2d import Sweep2D
174+
from measureit.visualization.heatmap_thread import Heatmap
175+
176+
in_params = [mock_parameters["voltage"], 0, 1, 0.5]
177+
out_params = [mock_parameters["gate"], -1, 1, 1.0]
178+
179+
sweep = Sweep2D(
180+
in_params,
181+
out_params,
182+
outer_delay=0.1,
183+
**fast_sweep_kwargs,
184+
)
185+
sweep.follow_param(mock_parameters["current"])
186+
187+
heatmap = Heatmap(sweep)
188+
heatmap.res_in = 3
189+
heatmap.res_out = 3
190+
heatmap.in_keys = [0.0, 0.5, 1.0]
191+
heatmap.out_keys = [-1.0, 0.0, 1.0]
192+
heatmap.in_step = 0.5
193+
heatmap.out_step = 1.0
194+
heatmap.figs_set = True
195+
heatmap.param_surfaces = {}
196+
197+
# Add data with specific values to verify column mapping
198+
# Value at each position uniquely identifies its (in_value, out_value) pair
199+
data_dict = {
200+
"forward": (
201+
np.array([0.0, 0.5, 1.0]), # Inner sweep x values
202+
np.array([1.0, 2.0, 3.0]), # Measurement values
203+
),
204+
"param_index": 1,
205+
"out_value": 0.0, # Middle row
206+
}
207+
heatmap.add_to_heatmap(data_dict)
208+
209+
data_array = heatmap.param_surfaces[1]["data"]
210+
211+
# The expected row for out_value=0.0 is row 1 (middle)
212+
# But due to the bug, it might be in a different row
213+
# Let's check column mapping is correct regardless of which row the data is in
214+
215+
# Find which row has non-zero data
216+
non_zero_rows = np.where(np.any(data_array != 0, axis=1))[0]
217+
assert len(non_zero_rows) == 1, \
218+
f"Expected exactly one row with data, found {len(non_zero_rows)}"
219+
220+
data_row = non_zero_rows[0]
221+
222+
# Verify column mapping: column 0 should have value 1.0, column 1 should have 2.0, etc.
223+
assert data_array[data_row, 0] == 1.0, \
224+
f"Column 0 (in_value=0.0) should have value 1.0, got {data_array[data_row, 0]}"
225+
assert data_array[data_row, 1] == 2.0, \
226+
f"Column 1 (in_value=0.5) should have value 2.0, got {data_array[data_row, 1]}"
227+
assert data_array[data_row, 2] == 3.0, \
228+
f"Column 2 (in_value=1.0) should have value 3.0, got {data_array[data_row, 2]}"

0 commit comments

Comments
 (0)