-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: new coreg gui features (part 2) #10085
Merged
larsoner
merged 48 commits into
mne-tools:main
from
GuillaumeFavelier:enh/coreg_feats_2
Dec 10, 2021
Merged
Changes from all commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
42c0ec1
add support for project_eeg and scale_by_distance
GuillaumeFavelier 6e18a4d
add support for mark_inside
GuillaumeFavelier 76302d1
add doc
GuillaumeFavelier d6f6a5f
fix
GuillaumeFavelier f256f53
fix
GuillaumeFavelier 7e75f3e
change default
GuillaumeFavelier cc281f8
sync _set_parameter with a lock
GuillaumeFavelier 6bd5da8
Merge branch 'main' into enh/coreg_feats
GuillaumeFavelier c9e91d2
rename
GuillaumeFavelier ca0d66f
simplify view options
GuillaumeFavelier 46413ff
simplify view options
GuillaumeFavelier bb0a7dc
fix
GuillaumeFavelier 794a772
Merge branch 'main' into enh/coreg_feats
GuillaumeFavelier 54e435f
fix
GuillaumeFavelier 9e3c434
simplify view options
GuillaumeFavelier 513dd4e
improve fit_icp log
GuillaumeFavelier 1ca1191
refactor
GuillaumeFavelier 42ed4fa
add more infos
GuillaumeFavelier 20a39f7
add _status_bar_show_message
GuillaumeFavelier fb35d62
enable high_res_head in view_options
GuillaumeFavelier e233717
update mne_coreg commands
GuillaumeFavelier 135d8ca
require pyvista>=0.32
GuillaumeFavelier 0d8eb30
change hpi coils scaling
GuillaumeFavelier d723cf4
TST: use threading.Lock
GuillaumeFavelier 0bac9b4
TST:add timer callback to _redraw_sensors
GuillaumeFavelier add6a6e
compatibility with notebook
GuillaumeFavelier 7fb8ce1
introduce new feats [ci skip]
GuillaumeFavelier 2d6fcc4
Merge branch 'main' into enh/coreg_feats_2
GuillaumeFavelier 091ee8f
refactor
GuillaumeFavelier f95c365
rename
GuillaumeFavelier 937a2f9
add _update_fids_dist
GuillaumeFavelier 0defce8
rename to _estimate_distance_to_fiducials
GuillaumeFavelier 53e034b
rename
GuillaumeFavelier 11f7872
add _get_point_distance
GuillaumeFavelier f7e6105
refactor
GuillaumeFavelier 96b67b2
concatenate log and benchmark
GuillaumeFavelier 26d87b3
increase timeout
GuillaumeFavelier d513ed2
use QTimer
GuillaumeFavelier 69310e2
disable fitting accordingly
GuillaumeFavelier f49f6cf
Merge branch 'main' into enh/coreg_feats_2
GuillaumeFavelier e68afff
refactor _display_message
GuillaumeFavelier 6d8d113
start migration to status bar
GuillaumeFavelier bd11c79
migrate msg to status bar
GuillaumeFavelier 903380e
fix
GuillaumeFavelier 3f8bd35
update more
GuillaumeFavelier 97ade81
fix
GuillaumeFavelier 2d4b4b7
TST: double call to _process_events
GuillaumeFavelier 371b86a
Try again
GuillaumeFavelier File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -219,6 +219,7 @@ def _get_default(var, val): | |
|
||
# configure UI | ||
self._reset_fitting_parameters() | ||
self._configure_status_bar() | ||
self._configure_dock() | ||
self._configure_picking() | ||
|
||
|
@@ -242,9 +243,11 @@ def _get_default(var, val): | |
False: dict(azimuth=180, elevation=90)} # left | ||
self._renderer.set_camera(distance=None, **views[self._lock_fids]) | ||
self._redraw() | ||
# XXX: internal plotter/renderer should not be exposed | ||
if not self._immediate_redraw: | ||
self._renderer.plotter.add_callback( | ||
self._redraw, self._refresh_rate_ms) | ||
self._renderer.plotter.show_axes() | ||
if standalone: | ||
_qt_app_exec(self._renderer.figure.store["app"]) | ||
|
||
|
@@ -373,6 +376,8 @@ def _set_point_weight(self, weight, point): | |
if point in funcs.keys(): | ||
getattr(self, funcs[point])(weight > 0) | ||
setattr(self, f"_{point}_weight", weight) | ||
setattr(self._coreg, f"_{point}_weight", weight) | ||
self._update_distance_estimation() | ||
|
||
@observe("_subjects_dir") | ||
def _subjects_dir_changed(self, change=None): | ||
|
@@ -402,6 +407,7 @@ def _lock_fids_changed(self, change=None): | |
if self._lock_fids: | ||
self._forward_widget_command(view_widgets, "set_enabled", True) | ||
self._display_message() | ||
self._update_distance_estimation() | ||
else: | ||
self._forward_widget_command(view_widgets, "set_enabled", False) | ||
self._display_message("Picking fiducials - " | ||
|
@@ -415,6 +421,7 @@ def _lock_fids_changed(self, change=None): | |
def _fiducials_file_changed(self, change=None): | ||
fids, _ = read_fiducials(self._fiducials_file) | ||
self._coreg._setup_fiducials(fids) | ||
self._update_distance_estimation() | ||
self._reset() | ||
self._set_lock_fids(True) | ||
|
||
|
@@ -530,6 +537,7 @@ def _redraw(self, verbose=None): | |
draw_map[key]() | ||
self._redraws_pending.clear() | ||
self._renderer._update() | ||
self._renderer._process_events() # necessary for MacOS? | ||
|
||
def _on_mouse_move(self, vtk_picker, event): | ||
if self._mouse_no_mvt: | ||
|
@@ -541,7 +549,7 @@ def _on_button_press(self, vtk_picker, event): | |
def _on_button_release(self, vtk_picker, event): | ||
if self._mouse_no_mvt > 0: | ||
x, y = vtk_picker.GetEventPosition() | ||
# XXX: plotter/renderer should not be exposed if possible | ||
# XXX: internal plotter/renderer should not be exposed | ||
plotter = self._renderer.figure.plotter | ||
picked_renderer = self._renderer.figure.plotter.renderer | ||
# trigger the pick | ||
|
@@ -589,11 +597,22 @@ def _reset_fiducials(self): | |
|
||
def _omit_hsp(self): | ||
self._coreg.omit_head_shape_points(self._omit_hsp_distance / 1e3) | ||
n_omitted = np.sum(~self._coreg._extra_points_filter) | ||
n_remaining = len(self._coreg._dig_dict['hsp']) - n_omitted | ||
self._update_plot("hsp") | ||
self._update_distance_estimation() | ||
self._display_message( | ||
f"{n_omitted} head shape points omitted, " | ||
f"{n_remaining} remaining.") | ||
|
||
def _reset_omit_hsp_filter(self): | ||
self._coreg._extra_points_filter = None | ||
self._coreg._update_params(force_update_omitted=True) | ||
self._update_plot("hsp") | ||
self._update_distance_estimation() | ||
n_total = len(self._coreg._dig_dict['hsp']) | ||
self._display_message( | ||
f"No head shape point is omitted, the total is {n_total}.") | ||
|
||
def _update_plot(self, changes="all"): | ||
# Update list of things that need to be updated/plotted (and maybe | ||
|
@@ -638,11 +657,9 @@ def _lock_plot(self): | |
self._plot_locked = old_plot_locked | ||
|
||
def _display_message(self, msg=""): | ||
if "msg" not in self._actors: | ||
self._actors["msg"] = self._renderer.text2d(0, 0, msg) | ||
else: | ||
self._actors["msg"].SetInput(msg) | ||
self._renderer._update() | ||
self._status_msg.set_value(msg) | ||
self._status_msg.show() | ||
self._status_msg.update() | ||
|
||
def _follow_fiducial_view(self): | ||
fid = self._current_fiducial.lower() | ||
|
@@ -659,6 +676,16 @@ def _update_fiducials(self): | |
self._forward_widget_command( | ||
["fid_X", "fid_Y", "fid_Z"], "set_value", val) | ||
|
||
def _update_distance_estimation(self): | ||
value = self._coreg._get_fiducials_distance_str() + '\n' + \ | ||
self._coreg._get_point_distance_str() | ||
dists = self._coreg.compute_dig_mri_distances() * 1e3 | ||
if self._hsp_weight > 0: | ||
value += "\nHSP <-> MRI (mean/min/max): "\ | ||
f"{np.mean(dists):.2f} "\ | ||
f"/ {np.min(dists):.2f} / {np.max(dists):.2f} mm" | ||
self._forward_widget_command("fit_label", "set_value", value) | ||
|
||
def _update_parameters(self): | ||
with self._lock_plot(): | ||
# rotation | ||
|
@@ -676,6 +703,7 @@ def _reset(self): | |
self._coreg.reset() | ||
self._update_plot() | ||
self._update_parameters() | ||
self._update_distance_estimation() | ||
|
||
def _forward_widget_command(self, names, command, value): | ||
names = [names] if not isinstance(names, list) else names | ||
|
@@ -697,6 +725,7 @@ def _set_sensors_visibility(self, state): | |
self._renderer._update() | ||
|
||
def _update_actor(self, actor_name, actor): | ||
# XXX: internal plotter/renderer should not be exposed | ||
self._renderer.plotter.remove_actor(self._actors.get(actor_name)) | ||
self._actors[actor_name] = actor | ||
|
||
|
@@ -786,6 +815,10 @@ def _add_head_hair(self): | |
self._coreg._get_processed_mri_points(res) | ||
|
||
def _fit_fiducials(self): | ||
if not self._lock_fids: | ||
self._display_message( | ||
"Fitting is disabled, lock the fiducials first.") | ||
return | ||
start = time.time() | ||
self._coreg.fit_fiducials( | ||
lpa_weight=self._lpa_weight, | ||
|
@@ -794,18 +827,25 @@ def _fit_fiducials(self): | |
verbose=self._verbose, | ||
) | ||
end = time.time() | ||
self._renderer._status_bar_show_message( | ||
self._display_message( | ||
f"Fitting fiducials finished in {end - start:.2f} seconds.") | ||
self._update_plot("sensors") | ||
self._update_parameters() | ||
self._update_distance_estimation() | ||
|
||
def _fit_icp(self): | ||
if not self._lock_fids: | ||
self._display_message( | ||
"Fitting is disabled, lock the fiducials first.") | ||
return | ||
Comment on lines
+837
to
+840
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the future we should properly disable in a UI way (button.setEnabled(False) in Qt parlance for example) the things that people are not supposed to use |
||
self._current_icp_iterations = 0 | ||
|
||
def callback(iteration, n_iterations): | ||
self._display_message(f"Fitting ICP - iteration {iteration + 1}") | ||
self._display_message( | ||
f"Fitting ICP - iteration {iteration + 1}") | ||
self._update_plot("sensors") | ||
self._current_icp_iterations = iteration | ||
self._current_icp_iterations = n_iterations | ||
self._update_distance_estimation() | ||
GuillaumeFavelier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._renderer._process_events() # allow a draw or cancel | ||
|
||
start = time.time() | ||
|
@@ -819,14 +859,16 @@ def callback(iteration, n_iterations): | |
) | ||
end = time.time() | ||
self._display_message() | ||
self._renderer._status_bar_show_message( | ||
self._display_message( | ||
f"Fitting ICP finished in {end - start:.2f} seconds and " | ||
f"{self._current_icp_iterations} iterations.") | ||
self._update_parameters() | ||
del self._current_icp_iterations | ||
|
||
def _save_trans(self, fname): | ||
write_trans(fname, self._coreg.trans) | ||
self._display_message( | ||
"{fname} transform file is saved.") | ||
|
||
def _load_trans(self, fname): | ||
mri_head_t = _ensure_trans(read_trans(fname, return_all=True), | ||
|
@@ -838,6 +880,9 @@ def _load_trans(self, fname): | |
tra=np.array([x, y, z]), | ||
) | ||
self._update_parameters() | ||
self._update_distance_estimation() | ||
self._display_message( | ||
f"{fname} transform file is loaded.") | ||
|
||
def _get_subjects(self, sdir=None): | ||
# XXX: would be nice to move this function to util | ||
|
@@ -931,15 +976,15 @@ def _configure_dock(self): | |
layout=layout, | ||
) | ||
self._widgets["grow_hair"] = self._renderer._dock_add_spin_box( | ||
name="Grow Hair", | ||
name="Grow Hair (mm)", | ||
value=self._grow_hair, | ||
rng=[0.0, 10.0], | ||
callback=self._set_grow_hair, | ||
layout=layout, | ||
) | ||
hlayout = self._renderer._dock_add_layout(vertical=False) | ||
self._widgets["omit_distance"] = self._renderer._dock_add_spin_box( | ||
name="Omit Distance", | ||
name="Omit Distance (mm)", | ||
value=self._omit_hsp_distance, | ||
rng=[0.0, 100.0], | ||
callback=self._set_omit_hsp_distance, | ||
|
@@ -1032,6 +1077,10 @@ def _configure_dock(self): | |
layout=hlayout, | ||
) | ||
self._renderer._layout_add_widget(layout, hlayout) | ||
self._widgets["fit_label"] = self._renderer._dock_add_label( | ||
value="", | ||
layout=layout, | ||
) | ||
self._widgets["icp_n_iterations"] = self._renderer._dock_add_spin_box( | ||
name="Number Of ICP Iterations", | ||
value=self._defaults["icp_n_iterations"], | ||
|
@@ -1110,6 +1159,10 @@ def _configure_dock(self): | |
self._renderer._layout_add_widget(layout, hlayout) | ||
self._renderer._dock_add_stretch() | ||
|
||
def _configure_status_bar(self): | ||
self._status_msg = self._renderer._status_bar_add_label("", stretch=1) | ||
self._status_msg.hide() | ||
|
||
def _clean(self): | ||
self._renderer = None | ||
self._coreg = None | ||
|
@@ -1118,6 +1171,8 @@ def _clean(self): | |
self._surfaces.clear() | ||
self._defaults.clear() | ||
self._head_geo = None | ||
self._redraw = None | ||
self._status_msg = None | ||
|
||
def close(self): | ||
"""Close interface and cleanup data structure.""" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer need
OrderedDict
because we are 3.7+. I think our existing ones are mostly there for cruft reasons