Skip to content

Commit b6d4216

Browse files
authored
Adds align profile, quick Bayesian exit, fixes bug in data background and BlittingSupport (#212)
1 parent 337eb43 commit b6d4216

7 files changed

Lines changed: 117 additions & 28 deletions

File tree

cpp/RAT

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies = [
1717
"numpy>=1.20",
1818
"prettytable>=3.9.0",
1919
"pydantic>=2.7.2",
20-
"scipy>=1.13.1",
20+
"scipy>=1.13.1, <=1.17.1",
2121
"strenum>=0.4.15 ; python_full_version < '3.11'",
2222
"tqdm>=4.66.5",
2323
]

ratapi/inputs.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def make_problem(project: ratapi.Project) -> ProblemDefinition:
243243
contrast_background_param = []
244244

245245
if background.type == TypeOptions.Data:
246-
contrast_background_param.append(project.data.index(background.source, True))
246+
# Background data is appended to contrast data so empty index
247247
if background.value_1 != "":
248248
contrast_background_param.append(project.background_parameters.index(background.value_1))
249249
# If we are using a data background, we add the background data to the contrast data
@@ -492,7 +492,7 @@ def check_indices(problem: ProblemDefinition) -> None:
492492

493493
source_param_lists = {
494494
"constant": "backgroundParams",
495-
"data": "data",
495+
"data": "backgroundParams",
496496
"function": "customFiles",
497497
}
498498

@@ -501,11 +501,13 @@ def check_indices(problem: ProblemDefinition) -> None:
501501

502502
# check source param is in range of the relevant parameter list
503503
param_list = getattr(problem, source_param_lists[background_type])
504-
source_index = background_data[0]
505-
if not 0 < source_index <= len(param_list):
504+
# For data, background data is appended to the contrast data so first index could
505+
# either be empty or be the index of an offset.
506+
if background_data and not 0 < background_data[0] <= len(param_list):
507+
first_entry_name = "data offset" if background_type == "data" else "source"
506508
raise IndexError(
507509
f'Entry {i} of contrastBackgroundParams has type "{background_type}" '
508-
f"and source index {source_index}, "
510+
f"and {first_entry_name} index {background_data[0]}, "
509511
f'which is outside the range of "{source_param_lists[background_type]}".'
510512
)
511513

ratapi/utils/convert.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,33 @@ def zip_if_several(*params) -> tuple | list[tuple]:
6666
return zip(*params, strict=False)
6767
return [params]
6868

69+
def __opaque_to_string(opaque):
70+
"""Extract java string from MATLABOpaque object.
71+
72+
Parameters
73+
----------
74+
opaque : MATLABOpaque
75+
object containing java string.
76+
77+
Returns
78+
-------
79+
str
80+
string extracted from object.
81+
82+
Raises
83+
------
84+
ValueError
85+
Raised if there is no java string array in the object.
86+
"""
87+
entries = opaque[0]
88+
for entry in entries:
89+
if isinstance(entry, ndarray):
90+
break
91+
else:
92+
raise ValueError("No array in MatlabOpaque")
93+
94+
return bytes(entry[7:]).decode("ascii")
95+
6996
def read_param(names, constrs, values, fits):
7097
"""Read in a parameter list from the relevant keys, and fix constraints for non-fit parameters.
7198
@@ -217,11 +244,11 @@ def fix_invalid_constraints(name: str, constrs: tuple[float, float], value: floa
217244
# which is given as the byte data of a Java string; this consists of 7 metadata bytes (ignored)
218245
# and then the actual string characters (index [7:]) in ascii format (.decode("ascii"))
219246
if len(mat_project["contrastNames"]) == 1 and isinstance(mat_project["contrastNames"], MatlabOpaque):
220-
mat_project["contrastNames"] = bytes(mat_project["contrastNames"][0][3][7:]).decode("ascii")
247+
mat_project["contrastNames"] = __opaque_to_string(mat_project["contrastNames"])
221248
else:
222249
for i, contrast_name in enumerate(mat_project["contrastNames"]):
223250
if isinstance(contrast_name, MatlabOpaque):
224-
mat_project["contrastNames"][i] = bytes(contrast_name[0][3][7:]).decode("ascii")
251+
mat_project["contrastNames"][i] = __opaque_to_string(contrast_name)
225252

226253
# if just one contrast, resolNames is a string; fix that here
227254
if isinstance(mat_project["resolNames"], str):

ratapi/utils/plotting.py

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def _extract_plot_data(event_data: PlotEventData, q4: bool, show_error_bar: bool
6060
# Plot the reflectivity on plot (1,1)
6161
results["ref"].append([r[:, 0], r[:, 1] * mult])
6262

63+
results["error"].append([])
6364
if event_data.dataPresent[i]:
6465
sd_x = data[:, 0]
6566
sd_y, sd_e = map(lambda x: x * mult, (data[:, 1], data[:, 2]))
@@ -73,7 +74,7 @@ def _extract_plot_data(event_data: PlotEventData, q4: bool, show_error_bar: bool
7374
valid = np.ones(len(sd_e)).astype(bool)
7475
sd_e = errors
7576

76-
results["error"].append([sd_x[valid], sd_y[valid], sd_e[valid]])
77+
results["error"][-1].extend([sd_x[valid], sd_y[valid], sd_e[valid]])
7778

7879
results["sld"].append([])
7980
for j in range(len(sld)):
@@ -111,6 +112,7 @@ def plot_ref_sld_helper(
111112
show_legend: bool = True,
112113
shift_value: float = 100,
113114
animated=False,
115+
align_profile=False,
114116
):
115117
"""Clear the previous plots and updates the ref and SLD plots.
116118
@@ -160,6 +162,8 @@ def plot_ref_sld_helper(
160162
ref_plot.cla()
161163
sld_plot.cla()
162164

165+
if align_profile:
166+
_align_profiles(data, confidence_intervals)
163167
plot_data = _extract_plot_data(data, q4, show_error_bar, shift_value)
164168
for i, name in enumerate(data.contrastNames):
165169
ref_plot.plot(plot_data["ref"][i][0], plot_data["ref"][i][1], label=name, linewidth=1, animated=animated)
@@ -234,6 +238,54 @@ def plot_ref_sld_helper(
234238
plt.pause(0.005)
235239

236240

241+
def _align_profiles(data: PlotEventData, confidence_intervals: dict | None = None):
242+
"""Align SLD profiles and resampled layers.
243+
244+
Aligns the A/L SLD profiles so that the substrates line up by padding the
245+
start of any shorter than the longest profile.
246+
247+
Parameters
248+
----------
249+
data : PlotEventData
250+
The plot event data that contains all the information
251+
to generate the ref and sld plots
252+
confidence_intervals : dict or None, default None
253+
The Bayesian confidence intervals for reflectivity and SLD.
254+
Only relevant if the procedure used is Bayesian (NS or DREAM)
255+
"""
256+
slds = data.sldProfiles
257+
size = (len(slds), len(slds[0]))
258+
259+
# Find the length of the longest profile.
260+
lengths = [[sld.shape[0] for sld in sld_row] for sld_row in slds]
261+
max_value = np.max(lengths)
262+
max_index = np.unravel_index(np.argmax(lengths), shape=size)
263+
264+
max_x = slds[max_index[0]][max_index[1]][:, 0]
265+
max_x_value = max_x[-1]
266+
267+
for i in range(size[0]):
268+
for j in range(size[1]):
269+
cur_sld = slds[i][j]
270+
diff = max_value - cur_sld.shape[0]
271+
if diff:
272+
pad = np.zeros(diff)
273+
max_y = np.concatenate((pad, cur_sld[:, 1]))
274+
slds[i][j] = np.column_stack((max_x, max_y))
275+
276+
cur_resample_layer = data.resampledLayers[i][j]
277+
if not np.all(cur_resample_layer):
278+
total_length = sum(cur_resample_layer[:, 0])
279+
offset = max_x_value - total_length
280+
data.resampledLayers[i][j] = np.vstack(([offset, 0, 0], cur_resample_layer))
281+
282+
if confidence_intervals is not None:
283+
cur_ci = confidence_intervals["sld"][i][j]
284+
inter_a = np.concatenate((pad, cur_ci[0]))
285+
inter_b = np.concatenate((pad, cur_ci[1]))
286+
confidence_intervals["sld"][i][j] = (inter_a, inter_b)
287+
288+
237289
def plot_ref_sld(
238290
project: ratapi.Project,
239291
results: ratapi.outputs.Results | ratapi.outputs.BayesResults,
@@ -294,7 +346,7 @@ def plot_ref_sld(
294346
data.reflectivity = copy.deepcopy(results.reflectivity)
295347
data.shiftedData = results.shiftedData
296348
data.sldProfiles = copy.deepcopy(results.sldProfiles)
297-
data.resampledLayers = results.resampledLayers
349+
data.resampledLayers = copy.deepcopy(results.resampledLayers)
298350
data.dataPresent = ratapi.inputs.make_data_present(project)
299351
data.subRoughs = results.contrastParams.subRoughs
300352
data.resample = ratapi.inputs.make_resample(project)
@@ -355,6 +407,7 @@ def plot_ref_sld(
355407
show_grid=show_grid,
356408
show_legend=show_legend,
357409
shift_value=shift_value,
410+
align_profile=(project.geometry == "air/substrate" and project.model != "custom xy"),
358411
)
359412

360413
if return_fig:
@@ -533,13 +586,12 @@ def update_foreground(self, data):
533586
self.figure.canvas.restore_region(self.bg)
534587
plot_data = _extract_plot_data(data, self.q4, self.show_error_bar, self.shift_value)
535588

536-
offset = 2
537-
for i in range(
538-
0,
539-
len(self.figure.axes[0].lines),
540-
):
541-
self.figure.axes[0].lines[i].set_data(plot_data["ref"][i // offset][0], plot_data["ref"][i // offset][1])
542-
self.figure.axes[0].draw_artist(self.figure.axes[0].lines[i])
589+
offset = 0
590+
for i in range(len(data.contrastNames)):
591+
for _ in range(int(data.dataPresent[i]) + 1):
592+
self.figure.axes[0].lines[offset].set_data(plot_data["ref"][i][0], plot_data["ref"][i][1])
593+
self.figure.axes[0].draw_artist(self.figure.axes[0].lines[offset])
594+
offset += 1
543595

544596
i = 0
545597
for j in range(len(plot_data["sld"])):
@@ -553,12 +605,14 @@ def update_foreground(self, data):
553605
self.figure.axes[1].draw_artist(self.figure.axes[1].lines[i])
554606
i += 1
555607

556-
for i, container in enumerate(self.figure.axes[0].containers):
557-
self.adjust_error_bar(
558-
container, plot_data["error"][i][0], plot_data["error"][i][1], plot_data["error"][i][2]
559-
)
560-
self.figure.axes[0].draw_artist(container[2][0])
561-
self.figure.axes[0].draw_artist(container[0])
608+
i = 0
609+
for error in plot_data["error"]:
610+
if error:
611+
container = self.figure.axes[0].containers[i]
612+
self.adjust_error_bar(container, error[0], error[1], error[2])
613+
self.figure.axes[0].draw_artist(container[2][0])
614+
self.figure.axes[0].draw_artist(container[0])
615+
i += 1
562616

563617
self.figure.canvas.blit(self.figure.bbox)
564618
self.figure.canvas.flush_events()

tests/test_inputs.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -534,17 +534,18 @@ def test_background_params_source_indices(self, test_problem, background_type, b
534534
test_problem = request.getfixturevalue(test_problem)
535535
test_problem.contrastBackgroundParams = bad_value
536536
test_problem.contrastBackgroundTypes = [background_type]
537-
538537
source_param_lists = {
539538
"constant": "backgroundParams",
540-
"data": "data",
539+
"data": "backgroundParams",
541540
"function": "customFiles",
542541
}
543542

543+
first_entry_name = "data offset" if background_type == "data" else "source"
544+
544545
with pytest.raises(
545546
IndexError,
546547
match=f'Entry 0 of contrastBackgroundParams has type "{background_type}" '
547-
f"and source index {bad_value[0][0]}, "
548+
f"and {first_entry_name} index {bad_value[0][0]}, "
548549
f'which is outside the range of "{source_param_lists[background_type]}".',
549550
):
550551
check_indices(test_problem)

tests/test_plotting.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,14 @@ def test_plot_ref_sld(mock: MagicMock, input_project, reflectivity_calculation_r
204204
for sld, sld_results in zip(sldProfile, result_sld_profile, strict=False):
205205
assert (sld == sld_results).all()
206206

207+
for resampledLayers, reflectivity_results in zip(
208+
data.resampledLayers, reflectivity_calculation_results.resampledLayers, strict=False
209+
):
210+
for resam, resam_results in zip(resampledLayers, reflectivity_results, strict=False):
211+
assert (resam == resam_results).all()
212+
207213
assert data.modelType == input_project.model
208214
assert data.shiftedData == reflectivity_calculation_results.shiftedData
209-
assert data.resampledLayers == reflectivity_calculation_results.resampledLayers
210215
assert data.dataPresent.size == 0
211216
assert (data.subRoughs == reflectivity_calculation_results.contrastParams.subRoughs).all()
212217
assert data.resample.size == 0

0 commit comments

Comments
 (0)