Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

central_widget.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. import gc
  2. import logging
  3. import os
  4. import pathlib as pl
  5. import sys
  6. import traceback
  7. from inspect import currentframe, getframeinfo
  8. import pandas as pd
  9. import yaml
  10. from PyQt5.QtCore import pyqtSlot, QSettings, pyqtSignal, QObject, QUrl
  11. from PyQt5.QtGui import QDesktopServices
  12. from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMessageBox, QHBoxLayout, QGroupBox, QLabel, \
  13. QPushButton, QFileDialog, QTabWidget
  14. from matplotlib import pyplot as plt
  15. from view.idl_translation_core.ViewOverview import ExportMovie
  16. from view.python_core.flags import FlagsManager
  17. from view.python_core.foto import show_photo, get_foto1_data
  18. from view.python_core.io import read_check_yml_file
  19. from view.python_core.measurement_list import MeasurementList
  20. from view.python_core.misc import get_system_temp_dir
  21. from view.python_core.movies import export_movie
  22. from view.python_core.overviews import pop_show_overview
  23. from view.python_core.p1_class import get_p1
  24. from .ILTIS_transfer_dialog import ILTISTransferDialog
  25. from .application_settings import get_view_qsettings_manager
  26. from .data_manager import DataManager
  27. from .direct_load import DirectDataLoader
  28. from .file_selector_combobox import get_file_selector_combobox_using_settings
  29. from .flags_box import FlagsDisplayChoiceTabs
  30. from .gdm_visualization import GDMViz
  31. from .load_measurement import LoadMeasurementsFromVWSLogWindow, LoadMeasurementsFromListWindow
  32. from .logger import LoggerGroupBox
  33. from .main_function_widgets import MainFunctionAbstract, OverviewGenWidget
  34. from .setup_calcmethod_choice import SetupChoice
  35. class CentralWidget(QWidget):
  36. export_data_signal = pyqtSignal(list, list, pd.DataFrame, int, tuple, tuple, int, name="export data")
  37. reset_iltis_signal = pyqtSignal(name="reset ILTIS")
  38. def __init__(self, parent):
  39. super().__init__(parent)
  40. # declare windows that might be generated
  41. self.gdm_viz_window = None
  42. self.load_measurement_window = None
  43. self.main_function_widgets = {}
  44. self.misc_function_buttons = {}
  45. self.p1s = {}
  46. self.init_flags()
  47. self.current_measurement_label = None
  48. self.init_ui()
  49. self.measurement_list = None
  50. self.yml_file = None
  51. plt.ion()
  52. def __del__(self):
  53. del self.gdm_viz_window
  54. del self.load_measurement_window
  55. def init_flags(self):
  56. self.flags = FlagsManager()
  57. def init_ui(self):
  58. main_hbox = QHBoxLayout(self)
  59. main_functions_vboxlayout = QVBoxLayout()
  60. self.setup_choice_box = SetupChoice(self)
  61. self.setup_choice_box.update_LE_loadExp_flag_signal.connect(self.flag_update_request_gui)
  62. self.main_function_widgets["setup choice box"] = self.setup_choice_box
  63. main_functions_vboxlayout.addWidget(self.setup_choice_box)
  64. # --------------------------------------------------------------------------------------------------------------
  65. temp_hbox = QHBoxLayout()
  66. calcMethod_flags = ["LE_CalcMethod"]
  67. self.calcMethod_box = MainFunctionAbstract(
  68. parent=self, button_names={},
  69. flag_names=calcMethod_flags,
  70. flag_defaults=[self.flags[f] for f in calcMethod_flags],
  71. group_name="Choose the method for calculating signals "
  72. "(does not affect raw data loading and artifact correction)"
  73. )
  74. self.calcMethod_box.flag_update_signal.connect(self.flag_update_request_gui)
  75. self.main_function_widgets["calc method choice box"] = self.calcMethod_box
  76. temp_hbox.addWidget(self.calcMethod_box)
  77. button = QPushButton("Close all matplotlib figures")
  78. button.clicked.connect(self.close_all_matplotlib_figures)
  79. temp_hbox.addWidget(button)
  80. main_functions_vboxlayout.addLayout(temp_hbox)
  81. # --------------------------------------------------------------------------------------------------------------
  82. loader_tabs = QTabWidget(self)
  83. main_functions_vboxlayout.addWidget(loader_tabs)
  84. direct_loader = DirectDataLoader(self, self.setup_choice_box.get_current_LE_loadExp())
  85. direct_loader.data_loaded_signal.connect(self.direct_load_finalize)
  86. self.setup_choice_box.return_LE_loadExp.connect(direct_loader.refresh_layout)
  87. loader_tabs.addTab(direct_loader, "Direct load from raw file (no pre-requirements)")
  88. log_load_box = QWidget(self)
  89. log_load_box_layout = QVBoxLayout(log_load_box)
  90. new_vws_log_load = QPushButton("Load from new vws log file")
  91. new_vws_log_load.clicked.connect(self.load_from_new_vws_log)
  92. log_load_box_layout.addWidget(new_vws_log_load)
  93. choose_from_current_vws_log = QPushButton("Select from current vws.log")
  94. choose_from_current_vws_log.clicked.connect(self.choose_row_from_current_vws_log)
  95. log_load_box_layout.addWidget(choose_from_current_vws_log)
  96. loader_tabs.addTab(log_load_box, "Direct load from log file (no pre-requirements)")
  97. list_load_box = QWidget(self)
  98. list_load_vboxlayout = QVBoxLayout(list_load_box)
  99. yaml_loader = get_file_selector_combobox_using_settings()(
  100. groupbox_title="The YML file",
  101. file_filter="YML File(*.yml)",
  102. file_type="YML",
  103. parent=self,
  104. use_list_in_settings="yml_file_list",
  105. )
  106. yaml_loader.return_filename_signal.connect(self.load_yml_flags)
  107. list_load_vboxlayout.addWidget(yaml_loader)
  108. loading_hbox = QHBoxLayout()
  109. list_vbox = QVBoxLayout()
  110. new_load = QPushButton("Load from new list file")
  111. new_load.clicked.connect(self.load_from_new_list)
  112. list_vbox.addWidget(new_load)
  113. self.main_function_widgets["load_lst"] = new_load
  114. choose_from_current_list = QPushButton("Select row from current list")
  115. choose_from_current_list.clicked.connect(self.choose_row_from_current_list)
  116. self.main_function_widgets["select row from current list"] = choose_from_current_list
  117. list_vbox.addWidget(choose_from_current_list)
  118. new_vws_log_load = QPushButton("Load from new vws log file")
  119. new_vws_log_load.clicked.connect(self.load_from_new_vws_log)
  120. list_vbox.addWidget(new_vws_log_load)
  121. self.main_function_widgets["select row from new vws log file"] = new_vws_log_load
  122. choose_from_current_vws_log = QPushButton("Select from current vws.log")
  123. choose_from_current_vws_log.clicked.connect(self.choose_row_from_current_vws_log)
  124. self.main_function_widgets["select row from current vws log file"] = choose_from_current_vws_log
  125. list_vbox.addWidget(choose_from_current_vws_log)
  126. quick_load_from_current_lst_box_flags = ["STG_Measu"]
  127. quick_load_from_current_lst_box = MainFunctionAbstract(parent=self,
  128. group_name="Quick Load",
  129. button_names=["Quick Load from current list"],
  130. flag_names=quick_load_from_current_lst_box_flags,
  131. flag_defaults=[self.flags[f] for f in
  132. quick_load_from_current_lst_box_flags],
  133. stack_vertically=False)
  134. loading_hbox.addLayout(list_vbox)
  135. quick_load_from_current_lst_box.send_data.connect(self.quick_load_from_current_lst)
  136. quick_load_from_current_lst_box.flag_update_signal.connect(self.flag_update_request_gui)
  137. self.main_function_widgets["quick load from current list"] = quick_load_from_current_lst_box
  138. loading_hbox.addWidget(quick_load_from_current_lst_box)
  139. list_load_vboxlayout.addLayout(loading_hbox)
  140. selected_list_file_box = QGroupBox("List/VWS.LOG file selected", self)
  141. self.current_measurement_label = QLabel("None selected yet")
  142. layout = QHBoxLayout(selected_list_file_box)
  143. layout.addWidget(self.current_measurement_label)
  144. self.main_function_widgets["selected list file"] = selected_list_file_box
  145. list_load_vboxlayout.addWidget(selected_list_file_box)
  146. loader_tabs.addTab(list_load_box, "List load (pre-requirements: YML File, folder structure, "
  147. "measurement list files)")
  148. # --------------------------------------------------------------------------------------------------------------
  149. overview_gdm_hbox = QHBoxLayout()
  150. gen_overviews_box = OverviewGenWidget(parent=self, current_flags=self.flags)
  151. gen_overviews_box.send_data.connect(self.generate_overview)
  152. gen_overviews_box.flag_update_signal.connect(self.flag_update_request_gui)
  153. self.main_function_widgets["generate_overview"] = gen_overviews_box
  154. overview_gdm_hbox.addWidget(gen_overviews_box)
  155. # --------------------------------------------------------------------------------------------------------------
  156. gdm_viz_box_flags = ["RM_ROITrace"]
  157. gdm_viz_box = MainFunctionAbstract(
  158. parent=self, button_names=["Visualize GDM traces"],
  159. flag_names=gdm_viz_box_flags, flag_defaults=[self.flags[f] for f in gdm_viz_box_flags],
  160. group_name="Visualize GDM traces", stack_vertically=True
  161. )
  162. gdm_viz_box.send_data.connect(self.viz_gdm_traces)
  163. gdm_viz_box.flag_update_signal.connect(self.flag_update_request_gui)
  164. self.main_function_widgets["viz_gdm"] = gdm_viz_box
  165. overview_gdm_hbox.addWidget(gdm_viz_box)
  166. main_functions_vboxlayout.addLayout(overview_gdm_hbox)
  167. # --------------------------------------------------------------------------------------------------------------
  168. misc_functions = QGroupBox("Miscellaneous functions", self)
  169. misc_functions_hbox = QHBoxLayout(misc_functions)
  170. save_button = QPushButton("Save movie (legacy)", self)
  171. save_button.clicked.connect(self.save_movie)
  172. misc_functions_hbox.addWidget(save_button)
  173. self.misc_function_buttons["save_movie"] = save_button
  174. save_movie_new = QPushButton("Save Movie (new)")
  175. save_movie_new.clicked.connect(self.save_movie_new)
  176. misc_functions_hbox.addWidget(save_movie_new)
  177. self.misc_function_buttons["save_movie_new"] = save_movie_new
  178. button2 = QPushButton("Show foto1")
  179. button2.clicked.connect(self.show_foto1)
  180. misc_functions_hbox.addWidget(button2)
  181. self.misc_function_buttons["show_foto1"] = button2
  182. button3 = QPushButton("Button3")
  183. button3.clicked.connect(self.button3_func)
  184. misc_functions_hbox.addWidget(button3)
  185. self.misc_function_buttons["button3"] = button3
  186. main_functions_vboxlayout.addWidget(misc_functions)
  187. main_hbox.addLayout(main_functions_vboxlayout)
  188. # --------------------------------------------------------------------------------------------------------------
  189. # --------------------------------------------------------------------------------------------------------------
  190. right_vbox = QVBoxLayout()
  191. self.flag_display_choice = FlagsDisplayChoiceTabs(self, self.flags)
  192. for subgroup_name, subgroup_page in self.flag_display_choice.subgroup_pages.items():
  193. subgroup_page.return_flag_signal.connect(self.flag_update_request_gui)
  194. flags_group_box = QGroupBox("Flags Viewer/Editor/Saver")
  195. flags_vbox = QVBoxLayout(flags_group_box)
  196. header_hbox = QHBoxLayout()
  197. header_hbox.addWidget(QLabel("Tip: Click on flag names for description"))
  198. wiki_link_button = QPushButton("Go to VIEW WIKI")
  199. wiki_link_button.clicked.connect(self.go_to_wiki)
  200. header_hbox.addWidget(wiki_link_button)
  201. save_button = QPushButton("Write flags to file")
  202. save_button.clicked.connect(self.write_yml_file)
  203. header_hbox.addWidget(save_button)
  204. flags_vbox.addLayout(header_hbox)
  205. flags_vbox.addWidget(self.flag_display_choice)
  206. right_vbox.addWidget(flags_group_box)
  207. # --------------------------------------------------------------------------------------------------------------
  208. data_manager_group_box = QGroupBox("Data Manager")
  209. data_manager_vbox = QVBoxLayout(data_manager_group_box)
  210. flags_values2use = \
  211. ["LE_loadExp", "LE_CalcMethod", "STG_ReportTag", "STG_Measu"] + self.flags.compound_path_flags
  212. def temp(x):
  213. return lambda p1: p1.metadata.get(x, "N/A")
  214. flags_values2use_dict = {k: k for k in flags_values2use}
  215. p1_values2use = {
  216. "Component\nOdors": temp("odor"), "Stimulus": temp("stimulus"), "Stimulus\nConcentration": temp("odor_nr"),
  217. "No. of pulses in stimuli": lambda p1: p1.metadata["pulsed_stimuli_handler"].stimulus_frame.shape[0],
  218. "Stimulus pulse start times\nrelative to imaging start (s)":
  219. lambda p1: [x / pd.Timedelta(seconds=1)
  220. for x in p1.metadata["pulsed_stimuli_handler"].get_pulse_start_times()],
  221. "Stimulus pulse end times\nrelative to imaging start (s)":
  222. lambda p1: [x / pd.Timedelta(seconds=1)
  223. for x in p1.metadata["pulsed_stimuli_handler"].get_pulse_end_times()],
  224. "Measurement\nLabel": temp("ex_name"), "Raw File Name": temp("full_raw_data_path_str"),
  225. "No. of frames": temp("frames"),
  226. "Frames per second": lambda p1: p1.metadata.frequency,
  227. "No. of Pixels along X": temp("format_x"),
  228. "No. of Pixels along Y": temp("format_y"),
  229. }
  230. self.data_manager = DataManager(
  231. parent=self, flag_values_to_use=flags_values2use_dict, p1_values_to_use=p1_values2use, label_joiner="_",
  232. default_label_cols=["STG_Measu", "STG_ReportTag"],
  233. precedence_order=[
  234. "STG_ReportTag", "STG_Measu", "Measurement\nLabel",
  235. "Stimulus", "Stimulus\nConcentration", "Component\nOdors",
  236. "No. of pulses in stimuli", "Stimulus pulse start times\nrelative to imaging start (s)",
  237. "Stimulus pulse end times\nrelative to imaging start (s)",
  238. "No. of frames", "Frames per second", "No. of Pixels along X", "No. of Pixels along Y",
  239. "LE_loadExp", "LE_CalcMethod"]
  240. )
  241. self.data_manager.remove_data_signal.connect(self.remove_data)
  242. data_manager_vbox.addWidget(self.data_manager.ui_table)
  243. right_vbox.addWidget(data_manager_group_box)
  244. # --------------------------------------------------------------------------------------------------------------
  245. self.log_pte = LoggerGroupBox(self)
  246. right_vbox.addWidget(self.log_pte)
  247. main_hbox.addLayout(right_vbox)
  248. # disable all actions other than YML loader
  249. self.enable_disable_functions_all(
  250. enable=False, main_exceptions=["yml loader", "setup choice box", "calc method choice box"])
  251. def close_all_matplotlib_figures(self):
  252. plt.close("all")
  253. def enable_disable_functions_all(self, enable,
  254. main_exceptions=(), misc_exceptions=()):
  255. for main_function, main_function_widget in self.main_function_widgets.items():
  256. if main_function not in main_exceptions:
  257. main_function_widget.setEnabled(enable)
  258. for misc_button_name, misc_button in self.misc_function_buttons.items():
  259. if misc_button_name not in misc_exceptions:
  260. misc_button.setEnabled(enable)
  261. def enable_disable_functions(self, enable, main_functions=(), misc_functions=()):
  262. for main_function in main_functions:
  263. self.main_function_widgets[main_function].setEnabled(enable)
  264. for misc_button_name in misc_functions:
  265. self.misc_function_buttons[misc_button_name].setEnabled(enable)
  266. def get_sample_stim_onoff_values(self):
  267. sample_p1 = self.p1s[self.data_manager.get_all_internal_labels()[0]]
  268. dict2return = [sample_p1.metadata.stimulus_on,
  269. sample_p1.metadata.stimulus_end,
  270. sample_p1.metadata.stim2ON,
  271. sample_p1.metadata.stim2OFF]
  272. return dict2return
  273. @pyqtSlot(name="respond to data request from iltis")
  274. def spawn_export_dialog(self):
  275. if len(self.p1s) == 0:
  276. QMessageBox.critical(self, "No data loaded!", "Please load some data before transferring data to ILTIS")
  277. else:
  278. data_manager_df = self.data_manager.df
  279. all_metadata_columns = data_manager_df.columns.values.tolist()
  280. metadata_to_choose_from = set(all_metadata_columns) \
  281. - set(self.data_manager.defaultLabelCols + [self.data_manager.label_col_name])
  282. metadata_to_choose_from = \
  283. [x.replace("\n", "---") for x in all_metadata_columns if x in metadata_to_choose_from]
  284. self.transfer_dialog = ILTISTransferDialog(
  285. data_loaded_df=data_manager_df, metadata_to_choose_from=metadata_to_choose_from
  286. )
  287. self.transfer_dialog.send_data_signal.connect(self.export_data)
  288. self.transfer_dialog.show()
  289. self.write_status("Waiting for data selection before transfer to ILTIS")
  290. def export_data(self, indices, metadata_list_for_label):
  291. '''
  292. goes through indices (i.e. the selected measurements to move to ILTIS)
  293. and copies data (p1.raw1) and signals (p1.sig1) etc. into the variables for ILTIS
  294. i.e. raw_data_list, signals_list etc.
  295. '''
  296. raw_data_list = []
  297. signals_list = []
  298. n_frames_list = []
  299. dm_df = self.data_manager.df.copy()
  300. metadata_to_send = dm_df.iloc[indices]
  301. stim_onset = []
  302. stim_offset = []
  303. for label, metadata_row in metadata_to_send.iterrows():
  304. p1 = self.p1s[label]
  305. stimulus_frames = p1.pulsed_stimuli_handler.get_pulse_start_end_frames(allow_fractional_frames=True)
  306. if len(stimulus_frames):
  307. stim_onset, stim_offset = zip(*stimulus_frames)
  308. else:
  309. stim_onset = stim_offset = ()
  310. LE_loadExp = metadata_row["LE_loadExp"]
  311. le_label = self.data_manager.label_line_edits[label].text()
  312. # the inverse replacement was done for visualization purposes in self.spawn_export_dialog
  313. metadata_list_for_label = [x.replace("---", "\n") for x in metadata_list_for_label]
  314. other_metadata_to_send = metadata_row.loc[metadata_list_for_label].values.tolist()
  315. label_to_use = self.data_manager.label_joiner.join([le_label] + [str(x) for x in other_metadata_to_send])
  316. if LE_loadExp == 4:
  317. label_to_use = f"{label_to_use}_FURA({p1.metadata.lambda_nm} nm/Ratio)"
  318. metadata_to_send.loc[label, "Label to use"] = label_to_use
  319. raw_data_list.append(p1.raw1)
  320. signals_list.append(p1.sig1)
  321. n_frames_list.append(p1.metadata.frames)
  322. for path_flag in self.flags.compound_path_flags:
  323. if path_flag not in self.flags.compound_path_flags_with_defaults:
  324. metadata_to_send[path_flag] = self.flags[path_flag]
  325. self.export_data_signal.emit(
  326. raw_data_list, signals_list,
  327. metadata_to_send, max(n_frames_list), stim_onset, stim_offset, self.flags["RM_Radius"])
  328. @pyqtSlot(name="export all data")
  329. def export_data_all(self):
  330. """
  331. sends all data loaded into VIEW to ILTIS using export_data above
  332. """
  333. self.export_data(indices=slice(None), metadata_list_for_label=[])
  334. def write_status(self, msg):
  335. self.parent().statusBar().showMessage(msg)
  336. logging.getLogger("VIEW").info(msg)
  337. @pyqtSlot(str, str, name="flag_update_request_from_gui")
  338. def flag_update_request_gui(self, flag_name, flag_value):
  339. if not self.check_update_flags_and_gui({flag_name: flag_value}):
  340. sender = QObject.sender(self)
  341. sender.reset_flag(flag_name, self.flags[flag_name])
  342. def check_apply_gui_limitations(self, flags):
  343. if "LE_BleachCorrMethod" in flags and flags["LE_BleachCorrMethod"] == "log_pixelwise":
  344. self.flag_display_choice.block_flags_update_signals(True)
  345. QMessageBox.warning(
  346. self, "Unavailable bleach correction setting",
  347. "Pixelwise bleach correction is not supported in VIEW-GUI. Uniform bleach correction"
  348. "will be applied instead. "
  349. "If you want to turn bleach correction off, set the flag LE_BleachCorrMethod to None"
  350. )
  351. self.flag_display_choice.block_flags_update_signals(False)
  352. flags["LE_BleachCorrMethod"] = "log_uniform"
  353. return flags
  354. def check_update_flags_and_gui(self, flags):
  355. flags = self.check_apply_gui_limitations(flags)
  356. for flag_name, flag_value in flags.items():
  357. if self.flags.is_flag_known(flag_name):
  358. this_flag_dict = {flag_name: flag_value}
  359. self.write_status(f"[working] Updating flag {flag_name} to {flag_value}")
  360. try:
  361. self.flags.update_flags(this_flag_dict)
  362. except AssertionError as ase:
  363. self.flag_display_choice.block_flags_update_signals(True)
  364. QMessageBox.critical(self, f"Error setting flag {flag_name} to {flag_value}", str(ase))
  365. self.write_status(f"[failure] Updating flag {flag_name} to {flag_value}")
  366. self.flag_display_choice.block_flags_update_signals(False)
  367. return 0
  368. except (FileNotFoundError, OSError) as fnfe:
  369. self.flag_display_choice.block_flags_update_signals(True)
  370. QMessageBox.critical(self, f"Error setting flag {flag_name}",
  371. f"There is a problem with the current folder structure and the path flags "
  372. f"specified. Specifically, the value of the flag {flag_name} is inconsistent."
  373. f"\n------\nHere is the full error message:\n{fnfe}")
  374. self.write_status(f"[failure] Updating flag {flag_name} to {flag_value}")
  375. self.flag_display_choice.block_flags_update_signals(False)
  376. return 0
  377. self._update_functions_flags(this_flag_dict)
  378. self.flag_display_choice.set_flags(this_flag_dict)
  379. self.write_status(f"[success] Updating flag {flag_name} to {flag_value}")
  380. else:
  381. self.write_status(f"[info] Ignoring update request for unknown flag {flag_name}")
  382. return 1
  383. def _update_functions_flags(self, flags):
  384. for function_name, box in self.main_function_widgets.items():
  385. if hasattr(box, "update_flag_defaults"):
  386. box.update_flag_defaults(flags)
  387. @pyqtSlot(str, name="load yml flags")
  388. def load_yml_flags(self, yml_filename):
  389. self.write_status(f"[working] Reading flags from {yml_filename}")
  390. try:
  391. yml_flags = read_check_yml_file(yml_filename, dict)
  392. except (yaml.YAMLError, AssertionError) as e:
  393. QMessageBox.critical(self, f"Error parsing YML file!",
  394. f"An error was encountered while parsing {yml_filename}. "
  395. f"Please check its validity.")
  396. return 0
  397. # the first time data is loaded via YML file and measurement list files, have fresh flags
  398. if self.yml_file is None:
  399. self.init_flags()
  400. self.yml_file = yml_filename
  401. # STG_MotherOfAllFolders must be set before STG* flags so that they are properly interpreted
  402. self.check_update_flags_and_gui({"STG_MotherOfAllFolders": str(pl.Path(yml_filename).parent)})
  403. # remove to avoid double, possible wrong initialization of this flag
  404. if "STG_MotherOfAllFolders" in yml_flags:
  405. del yml_flags["STG_MotherOfAllFolders"]
  406. self.write_status(f"[success] Reading flags from {yml_filename}")
  407. self.write_status(f"[working] Initializing flags from {yml_filename}")
  408. if self.check_update_flags_and_gui(yml_flags):
  409. # enable all actions
  410. self.enable_disable_functions(
  411. enable=True, main_functions=["load_lst", "select row from new vws log file"])
  412. self.write_status(f"[success] Initializing flags from {yml_filename}")
  413. else:
  414. self.write_status(f"[failure] Initializing flags from {yml_filename}")
  415. self.check_update_flags_and_gui({"VIEW_batchmode": False})
  416. self.flags.initialize_compound_flags_with_defaults()
  417. # reset label
  418. self.current_measurement_label.setText("None selected yet")
  419. # disable these two functions until a measurement has been loaded from a list
  420. self.enable_disable_functions(
  421. enable=False, main_functions=(
  422. "select row from current list", "quick load from current list",
  423. "select row from current vws log file"
  424. )
  425. )
  426. @pyqtSlot(name="go to wiki")
  427. def go_to_wiki(self):
  428. QDesktopServices.openUrl(QUrl("https://github.com/galizia-lab/pyview/wiki"))
  429. @pyqtSlot(name="write yml file")
  430. def write_yml_file(self):
  431. filename, filters = QFileDialog.getSaveFileName(caption="Select a YML file for saving flags",
  432. filter="YML File(*.yml)",
  433. directory=self.get_default_directory(),
  434. parent=self)
  435. self.write_status(f"[working] Writing flags to {filename}")
  436. try:
  437. self.flags.write_flags_to_yml(filename)
  438. self.write_status(f"[success] Writing flags to {filename}")
  439. return
  440. except Exception as e:
  441. QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", str(e))
  442. def get_selected_data_p1(self):
  443. data_label = self.data_manager.get_selected_data_label()
  444. return self.p1s[data_label]
  445. @pyqtSlot(dict, FlagsManager)
  446. def direct_load_finalize(self, label_p1_mapping, flags_used):
  447. for label, p1 in label_p1_mapping.items():
  448. revised_label = self.data_manager.add_data(flags_used, p1, label)
  449. self.p1s[revised_label] = p1
  450. self.flags = flags_used
  451. self.yml_file = None
  452. self.enable_disable_functions(enable=True, main_functions=["generate_overview"],
  453. misc_functions=self.misc_function_buttons.keys())
  454. @pyqtSlot(name="launch lst file window")
  455. def load_from_new_list(self):
  456. self.load_measurement_window = LoadMeasurementsFromListWindow(
  457. self.flags["LE_loadExp"], default_directory_path=self.flags["STG_OdorInfoPath"])
  458. self.load_measurement_window.send_data_signal.connect(self.load_lst_data)
  459. self.load_measurement_window.show()
  460. self.write_status("Waiting for selection of measurement from 'Load Measurement' window")
  461. @pyqtSlot(name="launch vws log file window")
  462. def load_from_new_vws_log(self):
  463. data_path = self.flags.get_raw_data_dir_str()
  464. if not pl.Path(data_path).is_dir():
  465. data_path = str(pl.Path.home())
  466. self.load_measurement_window = LoadMeasurementsFromVWSLogWindow(
  467. self.flags["LE_loadExp"],
  468. default_directory_path=data_path)
  469. self.load_measurement_window.send_data_signal.connect(self.load_lst_data)
  470. self.load_measurement_window.show()
  471. self.write_status("Waiting for selection of measurement from 'Load Measurement' window")
  472. @pyqtSlot(MeasurementList, list, name="load lst data")
  473. def load_lst_data(self, measurement_list, selected_measus):
  474. self.measurement_list = measurement_list
  475. lst_or_log_filepath = pl.Path(measurement_list.last_measurement_list_fle)
  476. # in case of log load, self.measurement_list is not loaded from a list file, but initialized directly from a
  477. # log file. The value "lst_filename" above points to the VWS.LOG file.
  478. # During log load, flags may not have been initialized from a YML file. So, I am making sure here,
  479. # that the flag "STG_Datapath" is set so that data can be correctly loaded
  480. temp_dir = pl.Path(lst_or_log_filepath).parent / "temp_dir_for_view_analyses"
  481. flags_to_update = {}
  482. if lst_or_log_filepath.suffixes == [".vws", ".log"]:
  483. for flag in self.flags.compound_path_flags:
  484. try:
  485. if not pl.Path(self.flags[flag]).is_dir():
  486. temp_dir.mkdir(exist_ok=True)
  487. flags_to_update[flag] = str(temp_dir)
  488. except KeyError as ke:
  489. temp_dir.mkdir(exist_ok=True)
  490. flags_to_update[flag] = str(temp_dir)
  491. flags_to_update["STG_Datapath"] = pl.Path(lst_or_log_filepath).parent
  492. flags_to_update["STG_OdorInfoPath"] = str(temp_dir.parent)
  493. else:
  494. flags_to_update = {"STG_OdorInfoPath": str(measurement_list.get_STG_OdorInfoPath())}
  495. for k, v in flags_to_update.items():
  496. self.flag_update_request_gui(k, v)
  497. for measu in selected_measus:
  498. self.write_status(f"[working] Loading meta data for measu={measu} from {lst_or_log_filepath}.")
  499. try:
  500. p1_metadata, extra_metadata = measurement_list.get_p1_metadata_by_measu(measu)
  501. except AssertionError as ase:
  502. QMessageBox.critical(self, "Problem getting measurement metadata", str(ase))
  503. self.write_status(f"Problem getting measurement metadata")
  504. self.write_status(f"[failure] Loading meta data for measu={measu} from {lst_or_log_filepath}.")
  505. return
  506. self.write_status(f"[success] Loading meta data for measu={measu} from {lst_or_log_filepath}.")
  507. if not self.check_update_flags_and_gui({"STG_Measu": measu,
  508. "STG_ReportTag": measurement_list.get_STG_ReportTag()
  509. }):
  510. return
  511. self.write_status(f"[success] Loading meta data for measu={measu} from {lst_or_log_filepath}.")
  512. self.write_status(f"[working] Loading raw data for measu={measu} from {lst_or_log_filepath} with "
  513. f"LE_loadExp={self.flags['LE_loadExp']}.")
  514. try:
  515. p1 = get_p1(p1_metadata=p1_metadata, flags=self.flags, extra_metadata=extra_metadata)
  516. except FileNotFoundError as fnfe:
  517. QMessageBox.critical(self, "File Not Found", str(fnfe))
  518. self.write_status(f"[failure] Loading measurement data for measu={measu} from {lst_or_log_filepath}.")
  519. return
  520. self.write_status(f"[success] Loading raw data for measu={measu} from {lst_or_log_filepath} with "
  521. f"LE_loadExp={self.flags['LE_loadExp']}.")
  522. self.write_status(f"[working] Calculating signal for measu={measu} from {lst_or_log_filepath} with "
  523. f"LE_CalcMethod={self.flags['LE_CalcMethod']}.")
  524. p1.calculate_signals(self.flags)
  525. self.write_status(f"[success] Calculating signal for measu={measu} from {lst_or_log_filepath} with "
  526. f"LE_CalcMethod={self.flags['LE_CalcMethod']}.")
  527. label = self.flags.get_measurement_label(measurement_row=self.measurement_list.get_row_by_measu(measu))
  528. revised_label = self.data_manager.add_data(self.flags, p1, label)
  529. self.p1s[revised_label] = p1
  530. # update label indicating selected list file
  531. self.current_measurement_label.setText(str(lst_or_log_filepath))
  532. # enable all functions
  533. self.enable_disable_functions_all(enable=True)
  534. @pyqtSlot(str, dict, name="reload from last lst file")
  535. def quick_load_from_current_lst(self, button_name, flags):
  536. if not self.check_update_flags_and_gui(flags):
  537. return
  538. if self.measurement_list is None:
  539. QMessageBox.critical(
  540. self, "Error finding measurement",
  541. "A measurement from an LST/settings file has not yet been loaded. "
  542. "Please load a measurement from an LST/settings file first using the function "
  543. "'Load measurement from LST/settings file' above")
  544. measus = self.measurement_list.get_measus()
  545. measu_user_input = self.flags["STG_Measu"]
  546. if measu_user_input not in measus:
  547. QMessageBox.critical(
  548. self,
  549. "Measu not found!",
  550. f"Measu={measu_user_input} is not present in the current measurement list file"
  551. f"{self.measurement_list.last_measurement_list_fle}")
  552. try:
  553. self.load_lst_data(self.measurement_list,
  554. [measu_user_input])
  555. return
  556. except Exception as e:
  557. QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", str(e))
  558. @pyqtSlot(name="choose from current list")
  559. def choose_row_from_current_list(self):
  560. if self.measurement_list is None:
  561. QMessageBox.critical(self, "Error finding measurement",
  562. "A measurement from an LST/settings file has not yet been loaded. "
  563. "Please load a measurement from an LST/settings file first using the function "
  564. "'Load measurement from LST/settings file' above")
  565. try:
  566. self.load_measurement_window = LoadMeasurementsFromListWindow(
  567. self.flags["LE_loadExp"],
  568. default_directory_path=self.flags["STG_OdorInfoPath"],
  569. measurement_list=self.measurement_list)
  570. self.load_measurement_window.lst_file_selector.set_entry(self.measurement_list.last_measurement_list_fle)
  571. self.load_measurement_window.refresh_display()
  572. self.load_measurement_window.send_data_signal.connect(self.load_lst_data)
  573. self.load_measurement_window.show()
  574. self.write_status("Waiting for selection of measurement from 'Load Measurement' window")
  575. except Exception as e:
  576. QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", str(e))
  577. @pyqtSlot(name="choose from current vws log")
  578. def choose_row_from_current_vws_log(self):
  579. if self.measurement_list is None:
  580. QMessageBox.critical(self, "Error finding measurement",
  581. "A measurement from a VWS.LOG file has not yet been loaded. "
  582. "Please load a measurement from an VWS.LOG file first using the function "
  583. "'Load measurement from VWS.LOG file' above")
  584. try:
  585. self.load_measurement_window = LoadMeasurementsFromVWSLogWindow(
  586. self.flags["LE_loadExp"],
  587. # does not matter, as value is set in the next command
  588. default_directory_path=self.flags.get_raw_data_dir_str(),
  589. measurement_list=self.measurement_list)
  590. self.load_measurement_window.lst_file_selector.set_entry(self.measurement_list.last_measurement_list_fle)
  591. self.load_measurement_window.refresh_display()
  592. self.load_measurement_window.send_data_signal.connect(self.load_lst_data)
  593. self.load_measurement_window.show()
  594. self.write_status("Waiting for selection of measurement from 'Load Measurement' window")
  595. except Exception as e:
  596. QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", str(e))
  597. def get_default_directory(self):
  598. settings = get_view_qsettings_manager()
  599. current_file_list = settings.value("yml_file_list", type=list)
  600. if self.yml_file is not None:
  601. return str(pl.Path(self.yml_file).parent)
  602. elif len(current_file_list):
  603. return str(pl.Path(current_file_list[-1]).parent)
  604. else:
  605. return os.path.expanduser("~")
  606. def remove_data(self, label):
  607. self.reset_iltis_signal.emit()
  608. del self.p1s[label]
  609. gc.collect()
  610. @pyqtSlot(str, dict, bool, bool, name="generate overview")
  611. def generate_overview(self, button_name, flags, use_all_features, use_all_stimuli):
  612. if not self.check_update_flags_and_gui(flags):
  613. return
  614. self.write_status("[working] Generating overview")
  615. if button_name == "Generate(new)":
  616. try:
  617. pop_show_overview(flags=self.flags, p1=self.get_selected_data_p1(),
  618. label=self.data_manager.get_selected_data_label(),
  619. stimulus_number="all" if use_all_stimuli else None,
  620. feature_number="all" if use_all_features else None)
  621. self.write_status("[success] Generating overview")
  622. return
  623. except NotImplementedError as nie:
  624. QMessageBox.critical(self, "Not implemented Error",
  625. "Oops, the feature you requested is either invalid or not implemented."
  626. f"\n------\nHere is the full error message:\n{traceback.format_exc()}")
  627. except Exception as e:
  628. QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", traceback.format_exc())
  629. # elif # left here in case we need to add new buttons
  630. # remember to write a "[success]...." status and return after successful function call
  631. self.write_status("[failure] Generating overview")
  632. def save_movie(self):
  633. self.write_status("[working] Saving movie")
  634. ExportMovie(self.flags.to_series(), self.get_selected_data_p1())
  635. self.write_status("[success] Saving movie")
  636. def save_movie_new(self):
  637. self.write_status("[working] Save movie with new method")
  638. selected_data_label = self.data_manager.get_selected_data_label()
  639. selected_p1 = self.get_selected_data_p1()
  640. op_path = pl.Path(self.flags["STG_OdorReportPath"])
  641. if not op_path.is_dir():
  642. op_path = pl.Path(get_system_temp_dir())
  643. op_name_without_extension = op_path / f"{selected_data_label}(manually created)"
  644. try:
  645. self.write_status(f"[working] Save movie with new method to {str(op_name_without_extension)}")
  646. op_name_with_extension = export_movie(p1=selected_p1, flags=self.flags,
  647. full_filename_without_extension=str(op_name_without_extension))
  648. QMessageBox.information(self, "Movie saved successfully", f"Output file: {op_name_with_extension}")
  649. self.write_status(f"[success] Save movie with new method to {str(op_name_with_extension)}")
  650. return
  651. except Exception as e:
  652. exception_formatted = traceback.format_exception(*sys.exc_info())
  653. QMessageBox.critical(self, f"VIEW encountered an error!", "".join(exception_formatted))
  654. self.write_status(f"[failure] Save movie with new method")
  655. def show_foto1(self):
  656. self.write_status("[working] Show foto1")
  657. try:
  658. foto1_data = get_foto1_data(flags=self.flags, p1=self.get_selected_data_p1())
  659. show_photo(foto1_data)
  660. self.write_status("[success] Show foto1")
  661. return
  662. except Exception as e:
  663. QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", traceback.format_exc())
  664. self.write_status("[failure] Show foto1")
  665. def viz_gdm_traces(self):
  666. self.write_status("[working] Initializing GDM visualization window")
  667. try:
  668. self.gdm_viz_window = GDMViz(p1=self.get_selected_data_p1(), flags=self.flags)
  669. self.gdm_viz_window.show()
  670. self.write_status("[success] Initializing GDM visualization window")
  671. return
  672. except Exception as e:
  673. QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", traceback.format_exc())
  674. self.write_status("[failure] Initializing GDM visualization window")
  675. def button3_func(self):
  676. self.write_status("[working] button 3")
  677. # TODO: Clicking the button "button 3" bring the program here.
  678. # # --- implementation template ---
  679. # try:
  680. # pass # VIEW will try to run this code on button press
  681. # self.write_status("[success] <add function description>")
  682. # return
  683. # except Exception as e:
  684. #
  685. # QMessageBox.critical(self, f"VIEW encountered a {type(e).__name__}", traceback.format_exc())
  686. # self.write_status("[success] <add function description>")
  687. #
  688. # # ------
  689. # --- place holder code, remove after implementation ---
  690. frameinfo = getframeinfo(currentframe())
  691. QMessageBox.information(self, "Not implemented yet!", f"To implement this function, add code "
  692. f"in file {frameinfo.filename} "
  693. f"at line {frameinfo.lineno}")
  694. # ------