flags.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. '''
  2. (explanation as of Sept. 4th, 2019. Code by Ajay)
  3. flags control all parameters of an experiment that are not part of the imaging data itself,
  4. and the preferences for how to analyze the data,
  5. e.g. what kind of data, what kind of calc methods, which filters, etc.
  6. flags are given in a .yml file, that is located in the mother folder, i.e.
  7. within motherofallfolders.
  8. There is one .yml file for each experiment (= list of many animals with same settings)
  9. the full list of allowed flags is in view_flags_definition.csv
  10. e.g. ... Code/git_repos/VIEW/view/flags_and_metadata_definitions/view_flags_definition.csv
  11. The current format of flags is implemented as a class in python_core/flags.py:
  12. FlagsManager. It has the following features:
  13. Flags can be accessed by indexing, i.e., flags["LE_loadExp"].
  14. However, attribute access is not available, i.e., flags.LE_loadExp will give an error.
  15. When initialized, the object takes default values from view_flags_definition.csv.
  16. Flags can be updated using the method "update_flags".
  17. Usage with a dictionary: .update_flags({'mv_bitrate':'512K'})
  18. This method makes sure that flags are only updated to valid values.
  19. There is a method "init_from_ymlfile" which can be used to initialize flags from a yml file.
  20. There are several other methods in the class which are associated with flags,
  21. like writing flags to yml file,
  22. or reading flags from yml file.
  23. For view-offline usage, I would suggest the following:
  24. Initialize a FlagsManager: flags = FlagsManager()
  25. Update it with value from a yml file: flags.read_flags_from_yml(yml_file)
  26. Use flags by indexing it.
  27. If you need attribute access, convert it to a pandas series using
  28. flags_series = flags.to_series()
  29. '''
  30. import pkg_resources
  31. import pandas as pd
  32. import yaml
  33. import pathlib as pl
  34. from ast import literal_eval
  35. from matplotlib.colors import is_color_like, to_rgba
  36. from view.python_core.io import read_check_yml_file, write_yml
  37. from view.python_core.misc import interpret_string_as_boolean
  38. from view.python_core.paths import convert_to_path_for_current_os, check_get_file_existence_in_folder
  39. from view.python_core.measurement_list.io import get_format_specific_defs
  40. import logging
  41. import os
  42. def get_internal_flags_def():
  43. """
  44. Read and return internal flags definitions
  45. :return: pandas.DataFrame
  46. """
  47. # get the internal flag checks file depending on flags_type
  48. flags_def_XL = pkg_resources.resource_filename('view',
  49. "flags_and_metadata_definitions/view_flags_definition.csv")
  50. # read and return flag definitions
  51. flags_def_df = pd.read_csv(flags_def_XL, comment="#")
  52. # initialize descriptions with empty string if it has no entry
  53. flags_def_df["Flag Description"] = flags_def_df["Flag Description"].apply(lambda x: "" if pd.isna(x) else x)
  54. return flags_def_df
  55. def check_correct_flag(flag_value, flag_name: str, expected_value_type_str: str, flag_checks: str = "True",
  56. error_msg: str = "Not specified"):
  57. comma_index = expected_value_type_str.find(",")
  58. if comma_index == 0:
  59. expected_value_types = [eval(expected_value_type_str[1:])]
  60. elif comma_index == len(expected_value_type_str):
  61. expected_value_types = [eval(expected_value_type_str[:-1])]
  62. elif comma_index == -1:
  63. expected_value_types = [eval(expected_value_type_str)]
  64. else:
  65. expected_value_types = [eval(x) for x in expected_value_type_str.split(",")]
  66. if expected_value_types == [float, int] or expected_value_types == [int, float]:
  67. assert False, f"{expected_value_type_str} is an invalid type specification for flag={flag_name} as the" \
  68. f"types are interconvertible"
  69. if None in expected_value_types:
  70. expected_value_types.remove(None)
  71. expected_value_types = [None] + expected_value_types
  72. flag_problems = [None for _ in expected_value_types]
  73. for evt_index, expected_value_type in enumerate(expected_value_types):
  74. # convert value to float if type float was expected but type int was specified
  75. if expected_value_type is float and type(flag_value) is int:
  76. flag_value = float(flag_value)
  77. # if expected type is bool, accept "True", "TRUE", "False", "FALSE"
  78. elif expected_value_type is bool and type(flag_value) is str:
  79. try:
  80. flag_value = interpret_string_as_boolean(flag_value)
  81. except ValueError as ve:
  82. flag_problems[evt_index] = str(ve)
  83. # if expected type is bool, accept float/int values of 0 as False and 1 as True
  84. elif expected_value_type is bool and type(flag_value) in (int, float):
  85. if flag_value == 1:
  86. flag_value = True
  87. elif flag_value == 0:
  88. flag_value = False
  89. else:
  90. flag_problems[evt_index] = f"Could not interpret numerical value {flag_value} as bool"
  91. elif expected_value_type is None:
  92. if flag_value is None:
  93. pass # all good
  94. elif type(flag_value) is str:
  95. if flag_value.lower() == "none":
  96. flag_value = None
  97. else:
  98. flag_problems[evt_index] = f"Could not interpret string {flag_value} as None"
  99. else:
  100. flag_problems[evt_index] = f"Could not interpret value {flag_value} of type {type(flag_value)} as None"
  101. # if expected type is a tuple and the flag value is a string
  102. elif expected_value_type is tuple and type(flag_value) is str:
  103. try:
  104. flag_value = tuple(literal_eval(flag_value))
  105. except Exception as e:
  106. flag_problems[evt_index] = f"Could not interpret string value {flag_value} as a tuple!"
  107. # convert value int or float if required and possible
  108. elif expected_value_type in (float, int):
  109. try:
  110. flag_value = expected_value_type(pd.to_numeric(flag_value, downcast="integer"))
  111. except (ValueError, TypeError) as ve:
  112. flag_problems[evt_index] = f"Could not interpret value={flag_value} as a {expected_value_type}!"
  113. # if the flag value is none of the above, but matches requirement
  114. elif type(flag_value) is expected_value_type:
  115. pass # all good
  116. # failure to interpret <flag value> as <expected_value_type>
  117. else:
  118. flag_problem = f"flag {flag_name} was expected to be of type " \
  119. f"{expected_value_type_str}, got {type(flag_value)}"
  120. flag_problems.append(flag_problem)
  121. # further checks on the flag value, if type could be interpreted
  122. if not pd.isnull(flag_checks) and flag_problems[evt_index] is None:
  123. flag_checks = flag_checks.replace("‘", "\'").replace("’", "\'").replace("“", "\"").replace("”", "\"")
  124. if not eval(flag_checks.format(flag="flag_value")):
  125. flag_problems[evt_index] = error_msg.format(flag=flag_value, flag_name=flag_name)
  126. if flag_problems[evt_index] is None:
  127. break # if flag value was parsed without problem, there is no need to try to parse further
  128. # if <flag_value> could not be interpreted as any of the expected types and
  129. # there were problems interpreting flag_value as any expected type
  130. if all(x is not None for x in flag_problems):
  131. flag_problems_str = '\n- '.join(flag_problems)
  132. raise AssertionError(f"Could not set the value of the flag '{flag_name}' to '{flag_value}'. "
  133. f"One or more of the following problems ocurred.\n\n"
  134. f"1. Could not interpret '{flag_value}' as any of these: {expected_value_type_str}\n"
  135. f"2. '{flag_value}' did not meet the condition: {flag_checks}\n\n"
  136. f"More Info on the errors encountered:\n\n{flag_problems_str}")
  137. return flag_value
  138. def to_PIL_RGBA(color):
  139. if is_color_like(color):
  140. return tuple(to_rgba(color).astype())
  141. class FlagsManager(object):
  142. def __init__(self):
  143. super().__init__()
  144. self.flags = {}
  145. self.flags_def_df = get_internal_flags_def()
  146. self.flags_def_df.set_index("Flag Name", inplace=True)
  147. self.compound_path_flags = ["STG_OdorReportPath", "STG_OdorInfoPath", "STG_OdormaskPath",
  148. "STG_Datapath", "STG_ProcessedDataPath", "STG_OdorAreaPath"]
  149. self.compound_path_flags_with_defaults = ["STG_ProcessedDataPath", "STG_OdorAreaPath"]
  150. self.initialize_defaults()
  151. self.label_separator = "_"
  152. def initialize_defaults(self, which=None):
  153. """
  154. Initializes flags with internally defined defaults. If <which> is None, all flags are initialized, else only
  155. flags with names in the iterable <which>
  156. :param which: iterable or None
  157. """
  158. if which is None:
  159. df2use = self.flags_def_df
  160. else:
  161. mask = [x in which for x in self.flags_def_df.index.values]
  162. df2use = self.flags_def_df.loc[mask, :]
  163. flag_name_value_dict = {}
  164. for flag_name, (flag_subgroup, flag_description, selectable_options,
  165. flag_value_type, flag_default_value, flag_checks, error_msg) in df2use.iterrows():
  166. is_not_a_compound_path_flag = flag_name not in self.compound_path_flags
  167. if flag_name != "STG_MotherOfAllFolders" and \
  168. is_not_a_compound_path_flag and \
  169. not self.is_flag_deprecated(flag_name):
  170. flag_name_value_dict[flag_name] = flag_default_value
  171. self.update_flags(flag_name_value_dict)
  172. def __getitem__(self, item):
  173. return self.flags[item]
  174. def items(self):
  175. return self.flags.items()
  176. def _expand_path_relative2moaf(self, flag_value):
  177. possible_path = (pl.Path(self["STG_MotherOfAllFolders"]) / flag_value).resolve()
  178. return possible_path
  179. def update_flags(self, flags):
  180. """
  181. Checks the updates specified in {flags} and update if the flags values are okay.
  182. :param flags: dict-like
  183. :return: None
  184. """
  185. for flag_name, flag_value in flags.items():
  186. # if flag is unknown
  187. if not self.is_flag_known(flag_name):
  188. logging.getLogger("VIEW").info(f"Ignoring update request for unknown flag {flag_name}")
  189. continue
  190. if self.is_flag_deprecated(flag_name):
  191. logging.getLogger("VIEW").warning(f"Ignoring update request for deprecated flag {flag_name}")
  192. continue
  193. expected_value_type = self.flags_def_df.loc[flag_name, "Flag Value Type"]
  194. flags_checks = self.flags_def_df.loc[flag_name, "Flag Checks"]
  195. error_msg = self.flags_def_df.loc[flag_name, "Error Message"]
  196. flag_value_corrected = check_correct_flag(flag_value=flag_value,
  197. flag_name=flag_name,
  198. expected_value_type_str=expected_value_type,
  199. flag_checks=flags_checks,
  200. error_msg=error_msg)
  201. # additional actions are required when updating compound-path flags (STG**Path flags)
  202. if flag_name in self.compound_path_flags:
  203. if flag_name in self.compound_path_flags_with_defaults:
  204. self._check_update_compound_flag(flag_name, flag_value, must_exist=False)
  205. else:
  206. self._check_update_compound_flag(flag_name, flag_value, must_exist=True)
  207. # additional actions are required when updating STG_MotherOfAllFolders
  208. elif flag_name == "STG_MotherOfAllFolders":
  209. self._check_update_mother_of_all_folders(flag_value)
  210. else:
  211. self.flags[flag_name] = flag_value_corrected
  212. def is_flag_known(self, flag_name):
  213. """
  214. returns True if flag_name is defined in internal flags definition
  215. :param flag_name: str
  216. :return: bool
  217. """
  218. return flag_name in self.flags_def_df.index
  219. def is_flag_deprecated(self, flag_name):
  220. """
  221. Returns true if flag <flag_name> is deprecated
  222. :param flag_name: str
  223. :return: bool
  224. """
  225. return self.flags_def_df.loc[flag_name, "Flag Description"].lower()[:10] == "deprecated"
  226. def get_flag_values_by_subgroup(self, subgroup):
  227. flags_to_return_df = self.get_subgroup_definition(subgroup)
  228. return {x: self[x] for x in flags_to_return_df["Flag Name"].values}
  229. def _check_update_mother_of_all_folders(self, moaf):
  230. """
  231. Update flag STG_MotherOfAllFolders, reset values of STG_**Path to default values (="not set yet)
  232. :return:
  233. """
  234. possible_moaf_path = convert_to_path_for_current_os(moaf)
  235. assert possible_moaf_path.is_dir(), \
  236. ValueError("Error updating the flag STG_MotherOfAllFolders! The value specified for it does not point to an "
  237. "existing folder on the computer.")
  238. self.flags["STG_MotherOfAllFolders"] = str(possible_moaf_path)
  239. self.initialize_defaults(self.compound_path_flags)
  240. def _check_update_compound_flag(self, flag_name, flag_value, must_exist=True):
  241. assert flag_name in self.compound_path_flags, f"{flag_name} is not one of {self.compound_path_flags}"
  242. temp_path = convert_to_path_for_current_os(flag_value)
  243. if not temp_path.is_absolute():
  244. temp_path = self._expand_path_relative2moaf(str(temp_path))
  245. try:
  246. temp_path.mkdir(exist_ok=True)
  247. self.flags[flag_name] = str(temp_path)
  248. except FileNotFoundError as fnfe: # this happens when the parent of temp_path does not exist
  249. if must_exist:
  250. raise FileNotFoundError(f"I am not able to figure out the path for \"{flag_name}\"."
  251. f"Specifed value ({flag_value}) does not point to an existing folder, "
  252. f"neither does {temp_path}. "
  253. f"VIEW can unfortunately not work without this folder. Please check "
  254. f" and if required create the folder.")
  255. def initialize_compound_flags_with_defaults(self):
  256. flags2update = {}
  257. for flag_name in self.compound_path_flags_with_defaults:
  258. if flag_name not in self.flags:
  259. flags2update[flag_name] = self.flags_def_df.loc[flag_name, "Flag Default Value"]
  260. # if self.is_flag_known(flag_name) and not self.is_flag_deprecated(flag_name):
  261. #
  262. # flags_default_value = self.flags_def_df.loc[flag_name, "Flag Default Value"]
  263. # self.flags[flag_name] = str(self._expand_path_relative2moaf(flags_default_value))
  264. self.update_flags(flags2update)
  265. def check_compound_flag_initialization_status(self):
  266. return {flag: self.is_flag_state_default(flag) if flag in self.flags else False
  267. for flag in self.compound_path_flags}
  268. def to_series(self):
  269. return pd.Series(self.flags)
  270. def get_subgroup_definition(self, subgroup):
  271. """
  272. Returns a pandas.Dataframe which is the internal flags definition Dataframe restricted to the columns
  273. ("Flag Name", "Flag Default Value", "Flag Description") and rows where the column "Flag Subgroup" == <subgroup>.
  274. Deprecated flags are excluded.
  275. :param subgroup: str, a flag subgroup
  276. :return: pandas.Dataframe
  277. """
  278. assert subgroup in self.flags_def_df["Flag Subgroup"].values, f"Invalid Subgroup {subgroup} specified"
  279. flags_def_df_reset = self.flags_def_df.reset_index()
  280. def selection_criteria(df):
  281. subgroup_mask = df["Flag Subgroup"] == subgroup
  282. non_deprecated_mask = df["Flag Description"].apply(lambda s: s[:10].lower() != "deprecated")
  283. return subgroup_mask & non_deprecated_mask
  284. temp = flags_def_df_reset.loc[
  285. selection_criteria,
  286. ("Flag Name", "Flag Default Value", "Flag Description", "Selectable Options", "Flag Value Type")
  287. ]
  288. return temp.reset_index(drop=True)
  289. def get_flags_filtered_by_subgroup(self, subgroups: list):
  290. df = pd.DataFrame()
  291. for index, row in self.flags_def_df.iterrows():
  292. if row["Flag Subgroup"] in subgroups:
  293. temp_s = pd.Series()
  294. temp_s["Flag Name"] = row["Flag Name"]
  295. temp_s["Flag Subgroup"] = row["Flag Subgroup"]
  296. temp_s["Flag Value"] = self.flags[row["Flag Name"]]
  297. df = df.append(temp_s, ignore_index=True)
  298. df.set_index("Flag name", inplace=True)
  299. return df
  300. def get_subgroups(self):
  301. return self.flags_def_df["Flag Subgroup"].unique()
  302. def get_flag_subgroup(self, flag_name):
  303. return self.flags_def_df.loc[flag_name, "Flag Subgroup"]
  304. def read_flags_from_yml(self, yml_filename):
  305. """
  306. Reads and initializes flags from YML file
  307. :param yml_filename: str, path to the YML file
  308. :return: None
  309. """
  310. #! Any changes here must also be reflected in view.gui.central_widget.CentralWidget.load_yml_flags !
  311. yml_flags = read_check_yml_file(yml_filename, dict)
  312. # STG_MotherOfAllFolders must be set before STG* flags so that they are properly interpreted
  313. self.update_flags({"STG_MotherOfAllFolders": str(pl.Path(yml_filename).parent)})
  314. # remove to avoid double, possible wrong initialization of this flag
  315. if "STG_MotherOfAllFolders" in yml_flags:
  316. del yml_flags["STG_MotherOfAllFolders"]
  317. self.update_flags(yml_flags)
  318. self.initialize_compound_flags_with_defaults()
  319. def write_flags_to_yml(self, yml_filename):
  320. """
  321. Writes flags to a YML file
  322. :param yml_filename: str, path of YML file
  323. """
  324. dict2write = {}
  325. dict2write.update(self.flags)
  326. for flag_name in self.compound_path_flags:
  327. if flag_name in self.flags:
  328. if not self.is_flag_state_default(flag_name):
  329. path = pl.Path(self.flags[flag_name])
  330. if path.is_absolute():
  331. dict2write[flag_name] = os.path.relpath(path, self["STG_MotherOfAllFolders"])
  332. dict2write["STG_MotherOfAllFolders"] = "!!parent of this file will be used!!"
  333. for k, v in dict2write.items():
  334. if type(v) in (tuple,):
  335. dict2write[k] = str(v)
  336. write_yml(yml_filename=yml_filename, to_write=dict2write)
  337. def get_coor_dir_str(self):
  338. return self.flags.get("STG_OdormaskPath", "not set yet")
  339. def get_area_dir_str(self):
  340. return self.flags.get("STG_OdorAreaPath", "not set yet")
  341. def get_raw_data_dir_str(self):
  342. return self.flags.get("STG_Datapath", "not set yet")
  343. def get_list_dir_str(self):
  344. return self.flags.get("STG_OdorInfoPath", "not set yet")
  345. def get_op_dir_str(self):
  346. return self.flags.get("STG_OdorReportPath", "not set yet")
  347. def get_processed_data_dir_str(self):
  348. return self.flags["STG_ProcessedDataPath"]
  349. def get_processed_data_dir_path(self):
  350. return pl.Path(self.get_processed_data_dir_str())
  351. def get_processed_data_op_path(self, format_name):
  352. return pl.Path(self.get_op_dir_str()) / "ProcessedDataExported" / format_name
  353. def get_animal_op_dir_path(self):
  354. """
  355. Returns <STG_OdorReportPath>/<STG_ReportTag> as pathlib.Path object
  356. :return: pathlib.Path
  357. """
  358. return pl.Path(self.get_op_dir_str()) / f"{self.get_current_animal_id()}"
  359. def get_animal_op_dir(self):
  360. """
  361. Returns <STG_OdorReportPath>/<STG_ReportTag> as a string
  362. :return: str
  363. """
  364. return str(self.get_animal_op_dir_path())
  365. def get_archive_dir_str(self):
  366. return self["STG_TempArchivePath"]
  367. def get_move_corrected_data_path_for_animal(self, animal):
  368. return self.get_processed_data_dir_path() / "MovementCorrectedData" / animal
  369. def get_existing_filename_in_coor(self, extension, measurement_label=""):
  370. return check_get_file_existence_in_folder(folder=self.get_coor_dir_str(), possible_extensions=[extension],
  371. stems=self.get_file_stem_hierarchy(measurement_label),
  372. )
  373. def get_file_stem_hierarchy(self, measurement_label):
  374. animal_tag = self.get_roi_filename_stem_for_current_animal()
  375. return [animal_tag, f"{animal_tag}_{measurement_label}"]
  376. def get_existing_area_filepath(self, measurement_label=""):
  377. """
  378. Checks possible nomenclatures and formats for mask file for current flag values and returns one that exisits
  379. If None exist, raises FileNotFoundError
  380. :return: pathlib.Path
  381. """
  382. extension_hierarchy = [".area.tif", ".AREA.tif", ".Area", ".AREA"]
  383. filename = check_get_file_existence_in_folder(folder=self.get_area_dir_str(),
  384. possible_extensions=extension_hierarchy,
  385. stems=self.get_file_stem_hierarchy(measurement_label))
  386. if filename is None:
  387. filename = check_get_file_existence_in_folder(folder=self.get_coor_dir_str(),
  388. possible_extensions=extension_hierarchy,
  389. stems=self.get_file_stem_hierarchy(measurement_label))
  390. return filename
  391. def get_current_animal_id(self):
  392. return self["STG_ReportTag"]
  393. def get_existing_lst_file(self, animal_name=None):
  394. """
  395. Tests possible files names for current flag settings and returns existing one. Else None.
  396. :return: str or None
  397. """
  398. if animal_name is not None:
  399. self.update_flags({"STG_ReportTag": animal_name})
  400. possible_lst_extensions = get_format_specific_defs()["extension"].values
  401. return check_get_file_existence_in_folder(folder=self.get_list_dir_str(), stems=[self.get_current_animal_id()],
  402. possible_extensions=possible_lst_extensions)
  403. def get_lst_file_stem(self):
  404. """
  405. Returns the stem of the measurement list file given the current flag values
  406. :return: str
  407. """
  408. return str(pl.Path(self.get_list_dir_str()) / self.get_current_animal_id())
  409. def get_component_traces_file(self, animal):
  410. return str(self.get_processed_data_dir_path() / "ComponentTimeTraces" / f"{animal}.csv")
  411. def get_op_tapestries_dir(self):
  412. """
  413. Returns the name of the folder used to output overviews as a string
  414. :return: string
  415. """
  416. return str(pl.Path(self.get_op_dir_str()) / "tapestries")
  417. def get_op_movie_dir(self):
  418. """
  419. Returns the name of the folder used to output movies as a string
  420. :return: string
  421. """
  422. return str(self.get_animal_op_dir_path() / "movies")
  423. def get_gloDatamix_file_for_current_animal(self):
  424. """
  425. Returns the name of the file used to output gloDatamix for current animal
  426. :return: string
  427. """
  428. return str(self.get_animal_op_dir_path() / f"{self.get_current_animal_id()}.gloDatamix.csv")
  429. def get_pipeline_report_dir_for_animal(self, animal):
  430. return str(pl.Path(self.get_op_dir_str()) / "Pipeline Reports" / animal)
  431. def get_roi_filename_stem_for_animal(self, animal):
  432. return animal
  433. def get_roi_filename_stem_for_current_animal(self):
  434. return self.get_roi_filename_stem_for_animal(animal=self.get_current_animal_id())
  435. def clear_flags(self):
  436. self.flags = {}
  437. def copy(self):
  438. flags = FlagsManager()
  439. flags.flags = self.flags.copy()
  440. return flags
  441. def is_flag_state_default(self, flag_name):
  442. assert flag_name in self.flags_def_df.index, f"Unknown flag name {flag_name}"
  443. flag_subgroup, flag_description, selectable_values, flag_value_type, flag_default_value, flag_checks, error_msg \
  444. = self.flags_def_df.loc[flag_name, :]
  445. flag_value_is_inited = flag_name in self.flags
  446. flag_value_is_default = False # the value here does not matter
  447. if flag_value_is_inited:
  448. flag_value_is_default \
  449. = self.flags[flag_name] == check_correct_flag(
  450. flag_name=flag_name,
  451. flag_value=flag_default_value,
  452. error_msg=error_msg,
  453. expected_value_type_str=flag_value_type,
  454. flag_checks=flag_checks)
  455. return flag_value_is_default or not flag_value_is_inited
  456. def get_default_measurement_label(self):
  457. return f"{self.flags['STG_ReportTag']}{self.label_separator}{self['STG_Measu']}"
  458. def get_measurement_label(self, measurement_row):
  459. label = self.get_default_measurement_label()
  460. label_columns = self.flags["LE_labelColumns"]
  461. labels_to_exclude = ["Measu"]
  462. labels2use = [x for x in label_columns if x not in labels_to_exclude and x in measurement_row]
  463. for col in labels2use:
  464. label = f"{label}{self.label_separator}{measurement_row[col]}"
  465. return label
  466. def get_ctv_method_file(self):
  467. ctv_method_file = self["CTV_MethodFile"]
  468. if pl.Path(ctv_method_file).is_absolute() and pl.Path(ctv_method_file).is_file():
  469. return ctv_method_file
  470. elif not self.is_flag_state_default("STG_MotherOfAllFolders"):
  471. possible_file = self._expand_path_relative2moaf(self["CTV_MethodFile"])
  472. if possible_file.is_file():
  473. return str(possible_file)
  474. raise FileNotFoundError(f"Could not interpret the specified CTV Method File ({ctv_method_file}) "
  475. f"as a file")
  476. def get_bleach_corr_patch_size(self):
  477. """
  478. Interprets value of flag "LE_BleachPatchSize".
  479. :returns: tuple indicating patch size for bleach correction along x and y
  480. """
  481. flag_value = self["LE_BleachPatchSize"]
  482. if type(flag_value) is int:
  483. return flag_value, flag_value
  484. elif type(flag_value) is tuple:
  485. if len(flag_value) == 2:
  486. return flag_value
  487. else:
  488. raise ValueError(f"Flag `LE_BleachPatchSize` expects an integer or a tuple of two integers. "
  489. f"Got {flag_value}")
  490. raise ValueError(
  491. f"Could not interpret value set for flag `LE_BleachPatchSize`. Expected int or tuple, got {flag_value}")
  492. def reset_all_flags_in_subgroup_to_default(self, subgroup):
  493. def_df = self.get_subgroup_definition(subgroup).set_index("Flag Name")
  494. self.update_flags(def_df["Flag Default Value"].to_dict())
  495. def interpret_median_filter_params(self):
  496. return interpret_filter_params(
  497. filter_behaviour_switch=self["Data_Median_Filter"],
  498. size_in_space=self["Data_Median_Filter_space"],
  499. size_in_time=self["Data_Median_Filter_time"]
  500. )
  501. def interpret_mean_filter_params(self):
  502. return interpret_filter_params(
  503. filter_behaviour_switch=self["Data_Mean_Filter"],
  504. size_in_space=self["Data_Mean_Filter_space"],
  505. size_in_time=self["Data_Mean_Filter_time"]
  506. )
  507. def interpret_filter_params(filter_behaviour_switch, size_in_space, size_in_time):
  508. if filter_behaviour_switch == 1:
  509. size_in_space = 3 # old fixed values, for backwards compatibility
  510. size_in_time = 1
  511. elif filter_behaviour_switch == 2:
  512. size_in_space = 1 # old fixed values, for backwards compatibility
  513. size_in_time = 3
  514. elif filter_behaviour_switch == 3:
  515. size_in_space = max(1, size_in_space) # values from flags
  516. size_in_time = max(1, size_in_time) # values at least 1
  517. # (i.e., if I selected a size of 0 to switch off filtering, 1 is taken all the same, since 1 means no filter)
  518. else: # will result in no filtering
  519. size_in_time = None
  520. size_in_space = None
  521. return size_in_space, size_in_time