Skip to content

Commit 2997ecb

Browse files
authored
Merge pull request #197 from chrishalcrow/add-warning-save-curation
Add exit dialog if user has not saved curation
2 parents e405748 + 58582d8 commit 2997ecb

File tree

5 files changed

+88
-28
lines changed

5 files changed

+88
-28
lines changed

spikeinterface_gui/backend_qt.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,19 @@ def make_split(self, zone_index_1, zone_index_2, orientation, widgets_zone, shif
311311

312312
# used by to tell the launcher this is closed
313313
def closeEvent(self, event):
314+
315+
if not self.controller.current_curation_saved:
316+
reply = QT.QMessageBox.question(self, 'Confirmation',
317+
"You are daydreaming: your curation has not been saved. You can save it at the top of the 'CurationView'.\nDo you still want to quit?", QT.QMessageBox.Yes |
318+
QT.QMessageBox.No, QT.QMessageBox.No)
319+
if reply == QT.QMessageBox.Yes:
320+
# 2. Accept the event to allow closing
321+
pass
322+
else:
323+
# 3. Ignore the event to prevent closing
324+
event.ignore()
325+
return
326+
314327
self.main_window_closed.emit(self)
315328
event.accept()
316329

spikeinterface_gui/controller.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@ def __init__(self, analyzer=None, backend="qt", parent=None, verbose=False, save
3838
extra_unit_properties=None, skip_extensions=None, disable_save_settings_button=False):
3939
self.views = []
4040
skip_extensions = skip_extensions if skip_extensions is not None else []
41+
4142
self.skip_extensions = skip_extensions
4243
self.backend = backend
4344
self.disable_save_settings_button = disable_save_settings_button
45+
self.current_curation_saved = True
46+
4447
if self.backend == "qt":
4548
from .backend_qt import SignalHandler
4649
self.signal_handler = SignalHandler(self, parent=parent)
@@ -802,20 +805,23 @@ def construct_final_curation(self):
802805

803806
def save_curation_in_analyzer(self):
804807
if self.analyzer.format == "memory":
808+
print("Analyzer is an in-memory object. Cannot save curation file in it.")
805809
pass
806810
elif self.analyzer.format == "binary_folder":
807811
folder = self.analyzer.folder / "spikeinterface_gui"
808812
folder.mkdir(exist_ok=True, parents=True)
809813
json_file = folder / f"curation_data.json"
810814
with json_file.open("w") as f:
811815
json.dump(check_json(self.construct_final_curation()), f, indent=4)
816+
self.current_curation_saved = True
812817
elif self.analyzer.format == "zarr":
813818
import zarr
814819
zarr_root = zarr.open(self.analyzer.folder, mode='r+')
815820
if "spikeinterface_gui" not in zarr_root.keys():
816821
sigui_group = zarr_root.create_group("spikeinterface_gui", overwrite=True)
817822
sigui_group = zarr_root["spikeinterface_gui"]
818823
sigui_group.attrs["curation_data"] = check_json(self.construct_final_curation())
824+
self.current_curation_saved = True
819825

820826
def get_split_unit_ids(self):
821827
if not self.curation:

spikeinterface_gui/curationview.py

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ def _qt_export_json(self):
292292
with json_file.open("w") as f:
293293
curation_dict = check_json(self.controller.construct_final_curation())
294294
json.dump(curation_dict, f, indent=4)
295+
self.controller.current_curation_saved = True
295296

296297
# PANEL
297298
def _panel_make_layout(self):
@@ -469,6 +470,31 @@ def _panel_refresh(self):
469470
self.table_split.value = df
470471
self.table_split.selection = []
471472

473+
if not self.controller.current_curation_saved:
474+
self.ensure_save_warning_message()
475+
else:
476+
self.ensure_no_message()
477+
478+
def ensure_save_warning_message(self):
479+
480+
if self.layout[0].name == 'curation_save_warning':
481+
return
482+
483+
import panel as pn
484+
485+
alert_markdown = pn.pane.Markdown(
486+
f"""⚠️⚠️⚠️ Your curation is not saved""",
487+
hard_line_break=True,
488+
styles={"color": "red", "font-size": "16px"},
489+
name="curation_save_warning"
490+
)
491+
492+
self.layout.insert(0, alert_markdown)
493+
494+
def ensure_no_message(self):
495+
if self.layout[0].name == 'curation_save_warning':
496+
self.layout.pop(0)
497+
472498
def _panel_update_unit_visibility(self, event):
473499
unit_dtype = self.controller.unit_ids.dtype
474500
if self.active_table == "delete":
@@ -501,6 +527,7 @@ def _panel_unmerge(self, event):
501527

502528
def _panel_save_in_analyzer(self, event):
503529
self.save_in_analyzer()
530+
self.refresh()
504531

505532
def _panel_generate_json(self):
506533
# Get the path from the text input
@@ -511,36 +538,11 @@ def _panel_generate_json(self):
511538
with open(export_path, "w") as f:
512539
json.dump(curation_dict, f, indent=4)
513540

514-
return export_path
515-
516-
def _panel_get_delete_table_selection(self):
517-
selected_items = self.table_delete.selection
518-
if len(selected_items) == 0:
519-
return None
520-
else:
521-
return self.table_delete.value["removed"].values[selected_items].tolist()
522-
523-
def _panel_get_merge_table_row(self):
524-
selected_items = self.table_merge.selection
525-
if len(selected_items) == 0:
526-
return None
527-
else:
528-
return selected_items
541+
self.controller.current_curation_saved = True
529542

530-
def _panel_get_split_table_row(self):
531-
selected_items = self.table_split.selection
532-
if len(selected_items) == 0:
533-
return None
534-
else:
535-
return selected_items
543+
self.refresh()
536544

537-
def _panel_handle_shortcut(self, event):
538-
if event.data == "restore":
539-
self.restore_units()
540-
elif event.data == "unmerge":
541-
self.unmerge()
542-
elif event.data == "unsplit":
543-
self.unsplit()
545+
return export_path
544546

545547
def _panel_submit_to_parent(self, event):
546548
"""Send the curation data to the parent window"""
@@ -572,6 +574,38 @@ def _panel_submit_to_parent(self, event):
572574

573575
# Update the hidden div with the JavaScript code
574576
self.data_div.object = js_code
577+
# Submitting to parent is a way to "save" the curation (the parent can handle it)
578+
self.controller.current_curation_saved = True
579+
self.refresh()
580+
581+
def _panel_get_delete_table_selection(self):
582+
selected_items = self.table_delete.selection
583+
if len(selected_items) == 0:
584+
return None
585+
else:
586+
return self.table_delete.value["removed"].values[selected_items].tolist()
587+
588+
def _panel_get_merge_table_row(self):
589+
selected_items = self.table_merge.selection
590+
if len(selected_items) == 0:
591+
return None
592+
else:
593+
return selected_items
594+
595+
def _panel_get_split_table_row(self):
596+
selected_items = self.table_split.selection
597+
if len(selected_items) == 0:
598+
return None
599+
else:
600+
return selected_items
601+
602+
def _panel_handle_shortcut(self, event):
603+
if event.data == "restore":
604+
self.restore_units()
605+
elif event.data == "unmerge":
606+
self.unmerge()
607+
elif event.data == "unsplit":
608+
self.unsplit()
575609

576610
def _panel_on_unit_visibility_changed(self):
577611
for table in [self.table_delete, self.table_merge, self.table_split]:

spikeinterface_gui/unitlistview.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ def _qt_set_default_label(self, label):
193193
for unit_id in selected_unit_ids:
194194
self.controller.set_label_to_unit(unit_id, "quality", label)
195195

196+
self.notify_manual_curation_updated()
197+
196198
selected_rows = self._qt_get_selected_rows()
197199
for row in selected_rows:
198200
curation_label = self.table.item(row, self.label_columns[0])
@@ -703,6 +705,7 @@ def _panel_on_edit(self, event):
703705
if new_label == "":
704706
new_label = None
705707
self.controller.set_label_to_unit(unit_id, column, new_label)
708+
self.notify_manual_curation_updated()
706709
self.notifier.notify_active_view_updated()
707710

708711
def _panel_on_only_selection(self):
@@ -759,16 +762,19 @@ def _panel_handle_shortcut(self, event):
759762
for unit_id in selected_unit_ids:
760763
self.controller.set_label_to_unit(unit_id, "quality", "good")
761764
self.table.value.loc[selected_unit_ids, "quality"] = "good"
765+
self.notify_manual_curation_updated()
762766
self.refresh()
763767
elif event.data == "mua":
764768
for unit_id in selected_unit_ids:
765769
self.controller.set_label_to_unit(unit_id, "quality", "MUA")
766770
self.table.value.loc[selected_unit_ids, "quality"] = "MUA"
771+
self.notify_manual_curation_updated()
767772
self.refresh()
768773
elif event.data == "noise":
769774
for unit_id in selected_unit_ids:
770775
self.controller.set_label_to_unit(unit_id, "quality", "noise")
771776
self.table.value.loc[selected_unit_ids, "quality"] = "noise"
777+
self.notify_manual_curation_updated()
772778
self.refresh()
773779

774780

spikeinterface_gui/view_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def notify_channel_visibility_changed(self):
5757
self.notifier.notify_channel_visibility_changed()
5858

5959
def notify_manual_curation_updated(self):
60+
self.controller.current_curation_saved = False
6061
self.notifier.notify_manual_curation_updated()
6162

6263
def notify_time_info_updated(self):

0 commit comments

Comments
 (0)