create_measurement_list_VTK2021.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. from view.python_core.measurement_list import MeasurementList
  2. from view.python_core.measurement_list.importers import get_importer_class
  3. from view.python_core.flags import FlagsManager
  4. from collections import OrderedDict
  5. import pandas as pd
  6. import logging
  7. logging.basicConfig(level=logging.INFO)
  8. # ------------------- Some parameters about experimental setup, data structure and output file type --------------------
  9. # 3 for single wavelength Till Photonics Measurements
  10. # 4 for two wavelength Till Photonics Measurements
  11. # 20 for Zeiss Confocal Measurements
  12. LE_loadExp = 3
  13. # Mother of all Folders of your dataset
  14. # On Windows, if you copy paths from the file explorer, make sure the string below is always of the form r"......"
  15. #STG_STG_STG_MotherOfAllFolders = r"/home/ajay/Nextcloud/VTK_2021/bee/HS_210521_test"
  16. #STG_STG_STG_MotherOfAllFolders = r"/Users/galizia/Documents/DATA/VTK_test/YT_VTK"
  17. #STG_STG_STG_MotherOfAllFolders = r"/Users/galizia/Documents/DATA/HS_210521_test"
  18. STG_STG_STG_MotherOfAllFolders = r"/Users/galizia/Nextcloud/VTK_2021/Bee_alarm_2022"# 01_DATA
  19. # path of the "Data" folder in VIEW organization containing the data
  20. # On Windows, if you copy paths from the file explorer, make sure the string below is always of the form r"......"
  21. STG_Datapath = r"01_DATA"
  22. # path of the "Lists" folder in VIEW organization containing the list files
  23. # On Windows, if you copy paths from the file explorer, make sure the string below is always of the form r"......"
  24. STG_OdorInfoPath = r"02_LISTS"
  25. # Choose measurement list output extension among ".lst", ".lst.xlsx", ".settings.xlsx"
  26. # VIEW does not support writing .xls list files anymore (nonetheless, it can read them and revise/update them to .xlsx)
  27. measurement_output_extension = ".lst.xlsx"
  28. # ------------------- A dictionary containing default values for metadata.----------------------------------------------
  29. # ------------------- Only metadata included in this dictionary will be written ----------------------------------------
  30. # ----Note that columns of the output measeurement list files will have the same order as below.------------------------
  31. default_values = OrderedDict()
  32. default_values['Measu'] = 0 # unique identifier for each line, corresponds to item in TILL photonics log file
  33. default_values['Label'] = "none"
  34. default_values['Odour'] = 'odor?' # stimulus name, maybe extracted from label in the function "custom_func" below
  35. default_values['OConc'] = 0 # odor concentration, maybe extracted from label in the function "custom_func" below
  36. default_values['Analyze'] = -1 # whether to analyze in VIEWoff. Default 1
  37. default_values['Cycle'] = 0 # how many ms per frame
  38. default_values['DBB1'] = 'none' # file name of raw data
  39. default_values['UTC'] = 0 # recording time, extracted from file
  40. default_values['PxSzX'] = '4.6' # um per pixel, 1.5625 for 50x air objective, measured by Hanna Schnell July 2017 on Till vision system, with a binning of 8
  41. default_values['PxSzY'] = '4.6' # um per pixel, 1.5625 for 50x air objective, measured by Hanna Schnell July 2017 on Till vision system, with a binning of 8
  42. default_values['Lambda'] = 0 # wavelength of stimulus. In TILL, from .log file, In Zeiss LSM, from .lsm file
  43. # These will be automatically filed for LE_loadExp=4
  44. default_values['dbb2'] = 'none' # file name of raw data in dual wavelength recordings (FURA)
  45. # To include more columns, uncomment entries below and specify a default value.
  46. # #
  47. # block for first stimulus
  48. # default_values['StimON'] = -1 # stimulus onset, unit: frames, count starts at frame 1.
  49. # default_values['StimOFF'] = -1 # stimulus offset, unit: frames, count starts at frame 1.
  50. # default_values['StimLen'] = 0 # stimulus onset in ms from beginning - alternative to StimON
  51. # default_values['StimONms'] = -1 # stimulus length in ms - alternative to StimOFF
  52. # #
  53. # block for second stimulus
  54. # default_values['Stim2ON'] = 0 # stimulus onset, unit: frames, count starts at frame 1.
  55. # default_values['Stim2OFF'] = 0 # stimulus offset, unit: frames, count starts at frame 1.
  56. # default_values['Stim2Len'] = 0 # stimulus onset in ms from beginning - alternative to StimON
  57. # default_values['Stim2ONms'] = -1 # stimulus length in ms - alternative to StimOFF
  58. # #
  59. # default_values['Age'] = -1
  60. # default_values['Sex'] = 'o'
  61. # default_values['Side'] = 'none'
  62. # default_values['Comment'] = 'none'
  63. # #
  64. # default_values['MTime'] = 0
  65. # default_values['Control'] = 0
  66. # default_values['Pharma'] = 'none'
  67. # default_values['PhTime'] = 0
  68. # default_values['PhConc'] = 0
  69. # default_values['ShiftX'] = 0
  70. # default_values['ShiftY'] = 0
  71. # default_values['StimISI'] = 0
  72. # default_values['setting'] = 'none'
  73. # default_values['dbb3'] = 'none'
  74. # default_values['PosZ'] = 0
  75. # default_values['Countl'] = 0
  76. # default_values['slvFlip'] = 0
  77. # ----------------------------------------------------------------------------------------------------------------------
  78. # ----------------- A function used to modify list entries after automatic parsing of metadata -------------------------
  79. # ----------------- This function indicates what needs to be done for a row --------------------------------------------
  80. # ----------------- The same is internally applied to all rows of the measurement list----------------------------------
  81. def get_odorinfo_from_label(label):
  82. # format for label is: concentration to the right, with a minus sign
  83. # Odor next to it, separated by underscore
  84. # IMPERARIVE: only ONE "-"
  85. # is the information for a concentration present? Detect "-"
  86. parts = label.split("-")
  87. if len(parts) == 2:
  88. concentration = "-" + parts[1]
  89. else:
  90. concentration = '0'
  91. # now look at the remaining part, the last bit is the odour
  92. parts = parts[0]
  93. if (parts[-1] == '_'): #remove trailing underscore
  94. parts = parts[:-1]
  95. parts = parts.split("_")
  96. if len(parts) > 1:
  97. odor = parts[-1]
  98. else:
  99. odor = 'unknown'
  100. return [odor, concentration]
  101. def custom_func(list_row: pd.Series, animal_tag: str) -> pd.Series:
  102. list_row['StimON'] = 24
  103. list_row['StimLen'] = 1000
  104. list_row['Stim2ON'] = 36
  105. list_row['Stim2Len'] = 1000
  106. list_row['Comment'] = 'VTK2021'
  107. list_row['Line'] = 'bee'
  108. # Examples:
  109. # list_row["StimON"] = 25
  110. (list_row["Odour"],list_row["OConc"]) = get_odorinfo_from_label(list_row["Label"])
  111. # if list_row["Measu"]
  112. # get Odor from another file based on the value of <animal_tag> and list_row["Label"]
  113. return list_row
  114. # ----------------------------------------------------------------------------------------------------------------------
  115. # ------------------ A function defining the criteria for excluding measurements ---------------------------------------
  116. # ------------------ Currently applicable only for tillvision setups ---------------------------------------------------
  117. def measurement_filter(s):
  118. # exclude blocks that have in the name "Snapshot" or "Delta"
  119. # or that do not have any "_"
  120. name = s["Label"]
  121. label_not_okay = name.count('Snapshot') > 0 or name.count('Delta') > 0 or name.count('_') < 1
  122. label_okay = not label_not_okay
  123. # exclude blocks with less than two frames or no calibration
  124. atleast_two_frames = False
  125. if type(s["Timing_ms"]) is str:
  126. if len(s["Timing_ms"].split(' ')) >= 2 and s["Timing_ms"] != "(No calibration available)":
  127. atleast_two_frames = True
  128. return label_okay and atleast_two_frames
  129. # ______________________________________________________________________________________________________________________
  130. # ------------------ names of columns that will be overwritten by old values -------------------------------------------
  131. # ------ these will only be used if a measurement list file with the same name as current output file exists -----------
  132. overwrite_old_values = ["Line", "PxSzX", "PxSzY", "Age", "Sex", "Prefer",
  133. "Comment", "Analyze", "Odour", "OConc"]
  134. # ______________________________________________________________________________________________________________________
  135. if __name__ == "__main__":
  136. # initialize a FlagsManager object with values specified above
  137. flags = FlagsManager()
  138. flags.update_flags({"STG_STG_STG_MotherOfAllFolders": STG_STG_STG_MotherOfAllFolders,
  139. "STG_OdorInfoPath": STG_OdorInfoPath,
  140. "STG_Datapath": STG_Datapath})
  141. # initialize importer
  142. importer_class = get_importer_class(LE_loadExp)
  143. importer = importer_class(default_values)
  144. # open a dialog for choosing raw data files
  145. # this returns a dictionary where keys are animal tags (STG_ReportTag) and
  146. # values are lists of associated raw data files
  147. animal_tag_raw_data_mapping = importer.ask_for_files(default_dir=flags["STG_Datapath"])
  148. # make sure some files were chosen
  149. assert len(animal_tag_raw_data_mapping) > 0, IOError("No files were chosen!")
  150. for animal_tag, raw_data_files in animal_tag_raw_data_mapping.items():
  151. # automatically parse metadata
  152. metadata_df = importer.import_metadata(raw_data_files=raw_data_files,
  153. measurement_filter=measurement_filter)
  154. # inform user if no usable measurements were found
  155. if metadata_df.shape[0] == 0:
  156. logging.info(f"No usable measurements we found among the files "
  157. f"chosen for the animal {animal_tag}. Not creating a list file")
  158. else:
  159. # create a new Measurement list object from parsed metadata
  160. measurement_list = MeasurementList.create_from_df(LE_loadExp=LE_loadExp,
  161. df=metadata_df)
  162. # apply custom modifications
  163. measurement_list.update_from_custom_func(custom_func=custom_func, animal_tag=animal_tag)
  164. # set anaylze to 0 if raw data files don't exist
  165. flags.update_flags({"STG_ReportTag": animal_tag})
  166. measurement_list.sanitize(flags=flags,
  167. data_file_extensions=importer.movie_data_extensions)
  168. # construct the name of the output file
  169. out_file = f"{flags.get_lst_file_stem()}{measurement_output_extension}"
  170. # write measurement file to list
  171. measurement_list.write_to_list_file(lst_fle=out_file, columns2write=default_values.keys(),
  172. overwrite_old_values=overwrite_old_values)