Skip to content

Commit 7a401c4

Browse files
Merge branch 'mne-tools:main' into gsoc_commits
2 parents 6259404 + 704b393 commit 7a401c4

File tree

19 files changed

+85
-84
lines changed

19 files changed

+85
-84
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
repos:
22
# Ruff mne
33
- repo: https://github.com/astral-sh/ruff-pre-commit
4-
rev: v0.9.10
4+
rev: v0.11.0
55
hooks:
66
- id: ruff
77
name: ruff lint mne
@@ -33,7 +33,7 @@ repos:
3333

3434
# yamllint
3535
- repo: https://github.com/adrienverge/yamllint.git
36-
rev: v1.35.1
36+
rev: v1.36.2
3737
hooks:
3838
- id: yamllint
3939
args: [--strict, -c, .yamllint.yml]
@@ -82,7 +82,7 @@ repos:
8282

8383
# zizmor
8484
- repo: https://github.com/woodruffw/zizmor-pre-commit
85-
rev: v1.4.1
85+
rev: v1.5.1
8686
hooks:
8787
- id: zizmor
8888

doc/changes/devel/13174.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix bug where :func:`mne.export.export_raw` might allocate huge intermediate arrays unnecessarily, when padding data blocks during export to EDF format, by `Daniel McCloy`_.

doc/changes/devel/13178.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix bug with least-squares fitting of head origin using digitization points in :func:`mne.preprocessing.maxwell_filter`, by `Eric Larson`_.

doc/development/contributing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ Now we'll remove the *stable* version of MNE-Python and replace it with the
291291
the correct environment first (``conda activate mnedev``), and then do::
292292

293293
$ cd $INSTALL_LOCATION/mne-python # make sure we're in the right folder
294-
$ conda remove --force mne # the --force avoids dependency checking
294+
$ conda remove --force mne-base # the --force avoids dependency checking
295295
$ pip install -e .
296296

297297
The command ``pip install -e .`` installs a python module into the current

mne/_fiff/tests/test_reference.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,11 @@ def test_set_eeg_reference_rest():
312312
same = [raw.ch_names.index(raw.info["bads"][0])]
313313
picks = np.setdiff1d(np.arange(len(raw.ch_names)), same)
314314
trans = None
315-
sphere = make_sphere_model("auto", "auto", raw.info)
315+
# Use fixed values from old sphere fit to reduce lines changed with fixed algorithm
316+
sphere = make_sphere_model(
317+
[-0.00413508, 0.01598787, 0.05175598],
318+
0.09100286249131773,
319+
)
316320
src = setup_volume_source_space(pos=20.0, sphere=sphere, exclude=30.0)
317321
assert src[0]["nuse"] == 223 # low but fast
318322
fwd = make_forward_solution(raw.info, trans, src, sphere)

mne/bem.py

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,10 +1073,10 @@ def get_fitting_dig(info, dig_kinds="auto", exclude_frontal=True, verbose=None):
10731073

10741074

10751075
@verbose
1076-
def _fit_sphere_to_headshape(info, dig_kinds, verbose=None):
1076+
def _fit_sphere_to_headshape(info, dig_kinds, *, verbose=None):
10771077
"""Fit a sphere to the given head shape."""
10781078
hsp = get_fitting_dig(info, dig_kinds)
1079-
radius, origin_head = _fit_sphere(np.array(hsp), disp=False)
1079+
radius, origin_head = _fit_sphere(np.array(hsp))
10801080
# compute origin in device coordinates
10811081
dev_head_t = info["dev_head_t"]
10821082
if dev_head_t is None:
@@ -1105,36 +1105,16 @@ def _fit_sphere_to_headshape(info, dig_kinds, verbose=None):
11051105
return radius, origin_head, origin_device
11061106

11071107

1108-
def _fit_sphere(points, disp="auto"):
1108+
def _fit_sphere(points):
11091109
"""Fit a sphere to an arbitrary set of points."""
1110-
if isinstance(disp, str) and disp == "auto":
1111-
disp = True if logger.level <= 20 else False
1112-
# initial guess for center and radius
1113-
radii = (np.max(points, axis=1) - np.min(points, axis=1)) / 2.0
1114-
radius_init = radii.mean()
1115-
center_init = np.median(points, axis=0)
1116-
1117-
# optimization
1118-
x0 = np.concatenate([center_init, [radius_init]])
1119-
1120-
def cost_fun(center_rad):
1121-
d = np.linalg.norm(points - center_rad[:3], axis=1) - center_rad[3]
1122-
d *= d
1123-
return d.sum()
1124-
1125-
def constraint(center_rad):
1126-
return center_rad[3] # radius must be >= 0
1127-
1128-
x_opt = fmin_cobyla(
1129-
cost_fun,
1130-
x0,
1131-
constraint,
1132-
rhobeg=radius_init,
1133-
rhoend=radius_init * 1e-6,
1134-
disp=disp,
1135-
)
1136-
1137-
origin, radius = x_opt[:3], x_opt[3]
1110+
# linear least-squares sphere fit, see for example
1111+
# https://stackoverflow.com/a/78909044
1112+
# TODO: At some point we should maybe reject outliers first...
1113+
A = np.c_[2 * points, np.ones((len(points), 1))]
1114+
b = (points**2).sum(axis=1)
1115+
x, _, _, _ = np.linalg.lstsq(A, b, rcond=1e-6)
1116+
origin = x[:3]
1117+
radius = np.sqrt(x[0] ** 2 + x[1] ** 2 + x[2] ** 2 + x[3])
11381118
return radius, origin
11391119

11401120

mne/channels/tests/test_montage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1453,7 +1453,7 @@ def renamer(x):
14531453
assert (orig_pos != new_pos).all()
14541454

14551455
r0 = _fit_sphere(new_pos)[1]
1456-
assert_allclose(r0, [-0.001021, 0.014554, 0.041404], atol=1e-4)
1456+
assert_allclose(r0, [-0.001043, 0.01469, 0.041448], atol=1e-4)
14571457
# spot check: Fp1 and Fpz
14581458
assert_allclose(
14591459
new_pos[:2],

mne/dipole.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1573,7 +1573,7 @@ def fit_dipole(
15731573
# Find the best-fitting sphere
15741574
inner_skull = _bem_find_surface(bem, "inner_skull")
15751575
inner_skull = inner_skull.copy()
1576-
R, r0 = _fit_sphere(inner_skull["rr"], disp=False)
1576+
R, r0 = _fit_sphere(inner_skull["rr"])
15771577
# r0 back to head frame for logging
15781578
r0 = apply_trans(mri_head_t["trans"], r0[np.newaxis, :])[0]
15791579
inner_skull["r0"] = r0

mne/export/_edf.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,18 @@ def _export_raw(fname, raw, physical_range, add_ch_type):
6666
f"{pad_width / sfreq:.3g} seconds of edge values were appended to all "
6767
"channels when writing the final block."
6868
)
69-
data = np.pad(data, (0, int(pad_width)), "edge")
69+
orig_shape = data.shape
70+
data = np.pad(
71+
data,
72+
(
73+
(0, 0),
74+
(0, int(pad_width)),
75+
),
76+
"edge",
77+
)
78+
assert data.shape[0] == orig_shape[0]
79+
assert data.shape[1] > orig_shape[1]
80+
7081
annotations.append(
7182
EdfAnnotation(
7283
raw.times[-1] + 1 / sfreq, pad_width / sfreq, "BAD_ACQ_SKIP"

mne/forward/tests/test_make_forward.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,7 @@ def test_make_forward_dipole(tmp_path):
719719
# Make sure each coordinate is close to reference
720720
# NB tolerance should be set relative to snr of simulated evoked!
721721
assert_allclose(
722-
dip_fit.pos, dip_test.pos, rtol=0, atol=1e-2, err_msg="position mismatch"
722+
dip_fit.pos, dip_test.pos, rtol=0, atol=1.3e-2, err_msg="position mismatch"
723723
)
724724
assert dist < 1e-2 # within 1 cm
725725
assert corr > 0.985

0 commit comments

Comments
 (0)