create_measurement_list_lif.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. '''
  2. Creates .lst file from/for Leica .lif files.
  3. Author: Giovanni, April 2022, based on template in VIEW folder by Ajay & others
  4. Expected data structure:
  5. In the folder "01_DATA", each animal has a .lif file
  6. i.e. all measurements of one animal are in that single file
  7. There is a sister folder "02_LISTS" (created if not yet present)
  8. Location is in STG_MotherOfAllFolders (set it below)
  9. Output:
  10. In the folder "02_LISTS":
  11. There will be a file Animal.lst.xlsx, e.g. "glom17_210923_bee11.lst.xlsx"
  12. That file contains one line for each measurement.
  13. Measurements that have no time dimension (snapshots, z-stacks) will have "0" in the column "analyze"
  14. What to do next?
  15. In this file, change values that are global
  16. or insert a function that can extract odor name or concentration name from somewhere
  17. In the Animal.lst.xlsx file, correct/complete entries (e.g. odor names, odor concentrations)
  18. Make sure stimulus timing information is correct
  19. When you run this program again on the same dataset, and the Animal.lst.xlsx file is already present,
  20. some columns will NOT be overwritten, but will be taken from the previous .lst.xlsx file,
  21. protecting your manually entered information.
  22. Which columns? Specify them below in
  23. overwrite_old_values
  24. Why do I need a .lst.xlsx file?
  25. Load measurements in pyVIEW using this .lst file, so that stimulus information is correct
  26. For off-line analysis, information is taken from this file.
  27. Good to know:
  28. Information, where possible, is taken from the OME header of the incoming .lif file.
  29. If that information is wrong, incomplete, or else, modify the code
  30. '''
  31. from view.python_core.measurement_list import MeasurementList
  32. from view.python_core.measurement_list.importers import get_importer_class
  33. from view.python_core.flags import FlagsManager
  34. from collections import OrderedDict
  35. import pandas as pd
  36. import logging
  37. import pathlib as pl
  38. logging.basicConfig(level=logging.INFO)
  39. # ------------------ names of columns that will be overwritten by old values -------------------------------------------
  40. # -- if you run the same animal a second time!
  41. # ------ these will only be used if a measurement list file with the same name as current output file exists -----------
  42. overwrite_old_values = ["Line", "PxSzX", "PxSzY", "Age", "Sex", "Prefer",
  43. "Comment", "Analyze", "Odour", "OConc"]
  44. # ______________________________________________________________________________________________________________________
  45. # ------------------- Some parameters about experimental setup, data structure and output file type --------------------
  46. # 3 for single wavelength Till Photonics Measurements
  47. # 4 for two wavelength Till Photonics Measurements
  48. # 20 for Zeiss Confocal Measurements
  49. # 21 for Leica Confocal Measurements: .lif file
  50. LE_loadExp = 21 #21 for .lif file
  51. # Mother of all Folders of your dataset
  52. # On Windows, if you copy paths from the file explorer, make sure the string below is always of the form r"......"
  53. STG_MotherOfAllFolders = r'/Users/galizia/Nextcloud/VTK_2021/Lif_Data'
  54. #STG_MotherOfAllFolders = r'/Users/galizia/Documents/DATA/Marco_lif'
  55. # path of the "Data" folder in VIEW organization containing the data
  56. # On Windows, if you copy paths from the file explorer, make sure the string below is always of the form r"......"
  57. STG_Datapath = r"01_DATA"
  58. # path of the "Lists" folder in VIEW organization containing the list files
  59. # On Windows, if you copy paths from the file explorer, make sure the string below is always of the form r"......"
  60. STG_OdorInfoPath = r"02_LISTS"
  61. # Choose measurement list output extension among ".lst", ".lst.xlsx", ".settings.xlsx"
  62. # VIEW does not support writing .xls list files anymore (nonetheless, it can read them and revise/update them to .xlsx)
  63. measurement_output_extension = ".lst.xlsx"
  64. # ------------------- A dictionary containing default values for metadata.----------------------------------------------
  65. # ------------------- Only metadata included in this dictionary will be written ----------------------------------------
  66. # ----Note that columns of the output measeurement list files will have the same order as below.------------------------
  67. default_values = OrderedDict()
  68. default_values['Measu'] = 0 # unique identifier for each line, corresponds to item in TILL photonics log file
  69. default_values['Label'] = "none"
  70. default_values['Odour'] = 'odor?' # stimulus name, maybe extracted from label in the function "custom_func" below
  71. default_values['OConc'] = 0 # odor concentration, maybe extracted from label in the function "custom_func" below
  72. default_values['Analyze'] = -1 # whether to analyze in VIEWoff. Default -1, which means "not checked yet"
  73. default_values['Cycle'] = 0 # how many ms per frame
  74. default_values['DBB1'] = 'none' # file name of raw data
  75. default_values['UTC'] = 0 # recording time, extracted from file
  76. default_values['PxSzX'] = '0.0' # um per pixel, 1.5625 for 50x air objective, measured by Hanna Schnell July 2017 on Till vision system, with a binning of 8
  77. default_values['PxSzY'] = '0.0' # um per pixel, 1.5625 for 50x air objective, measured by Hanna Schnell July 2017 on Till vision system, with a binning of 8
  78. default_values['Lambda'] = 0 # wavelength of stimulus. In TILL, from .log file, In Zeiss LSM, from .lsm file
  79. # These will be automatically filed for LE_loadExp=4
  80. default_values['dbb2'] = 'none' # file name of raw data in dual wavelength recordings (FURA)
  81. # To include more columns, uncomment entries below and specify a default value.
  82. # #
  83. # block for first stimulus
  84. # default_values['StimON'] = -1 # stimulus onset, unit: frames, count starts at frame 1.
  85. # default_values['StimOFF'] = -1 # stimulus offset, unit: frames, count starts at frame 1.
  86. # default_values['StimLen'] = 0 # stimulus onset in ms from beginning - alternative to StimON
  87. # default_values['StimONms'] = -1 # stimulus length in ms - alternative to StimOFF
  88. # #
  89. # block for second stimulus
  90. # default_values['Stim2ON'] = 0 # stimulus onset, unit: frames, count starts at frame 1.
  91. # default_values['Stim2OFF'] = 0 # stimulus offset, unit: frames, count starts at frame 1.
  92. # default_values['Stim2Len'] = 0 # stimulus onset in ms from beginning - alternative to StimON
  93. # default_values['Stim2ONms'] = -1 # stimulus length in ms - alternative to StimOFF
  94. # #
  95. # default_values['Age'] = -1
  96. # default_values['Sex'] = 'o'
  97. # default_values['Side'] = 'none'
  98. # default_values['Comment'] = 'none'
  99. # #
  100. # default_values['MTime'] = 0
  101. # default_values['Control'] = 0
  102. # default_values['Pharma'] = 'none'
  103. # default_values['PhTime'] = 0
  104. # default_values['PhConc'] = 0
  105. # default_values['ShiftX'] = 0
  106. # default_values['ShiftY'] = 0
  107. # default_values['StimISI'] = 0
  108. # default_values['setting'] = 'none'
  109. # default_values['dbb3'] = 'none'
  110. # default_values['PosZ'] = 0
  111. # default_values['Countl'] = 0
  112. # default_values['slvFlip'] = 0
  113. # ----------------------------------------------------------------------------------------------------------------------
  114. # ----------------- A function used to modify list entries after automatic parsing of metadata -------------------------
  115. # ----------------- This function indicates what needs to be done for a row --------------------------------------------
  116. # ----------------- The same is internally applied to all rows of the measurement list----------------------------------
  117. def get_odorinfo_from_label(label):
  118. # format for file name (label) is:
  119. # odor_concentration_anything_else.tif
  120. # separating element is underscore
  121. # is the information for a concentration present? Detect "-"
  122. parts = label.split("_")
  123. if len(parts) > 1:
  124. odor = parts[0]
  125. concentration = parts[1]
  126. # in the case the name is odor_conc.tif:
  127. if concentration[-4:] == '.tif':
  128. concentration = concentration[:-4]
  129. else:
  130. odor = 'odor?'
  131. concentration = 'conc?'
  132. return [odor, concentration]
  133. def custom_func(list_row: pd.Series, animal_tag: str) -> pd.Series:
  134. #TODO
  135. #add info "analyze" and set to 0 for snapshots
  136. # Examples:
  137. # list_row["StimON"] = 25
  138. # list_row["Odour"] = get_odor_from_label(list_row["Label"])
  139. # if list_row["Measu"]
  140. # get Odor from another file based on the value of <animal_tag> and list_row["Label"]
  141. list_row["StimONms"] = '3000'
  142. list_row["StimLen"] = '2000'
  143. list_row["Comment"] = 'create_measurement_list_lif'
  144. list_row["Line"] = 'bee'
  145. #extract odor and concentration from name
  146. (list_row["Odour"],list_row["OConc"]) = get_odorinfo_from_label(list_row["Label"])
  147. try:
  148. float(list_row["OConc"])
  149. except: #Odour concentration is not a number, set to fictive 0
  150. list_row["OConc"] = '0.0'
  151. if list_row["Label"][-4:] == '.tif':
  152. list_row["Label"] = list_row["Label"][:-4]
  153. return list_row
  154. # ----------------------------------------------------------------------------------------------------------------------
  155. # ------------------ A function defining the criteria for excluding measurements ---------------------------------------
  156. # ------------------ Currently applicable only for tillvision setups ---------------------------------------------------
  157. def measurement_filter(s):
  158. # exclude blocks that have in the name "Snapshot" or "Delta"
  159. # or that do not have any "_"
  160. name = s["Label"]
  161. label_not_okay = name.count('Snapshot') > 0 or name.count('Delta') > 0 or name.count('_') < 1
  162. label_okay = not label_not_okay
  163. # exclude blocks with less than two frames or no calibration
  164. atleast_two_frames = False
  165. if type(s["Timing_ms"]) is str:
  166. if len(s["Timing_ms"].split(' ')) >= 2 and s["Timing_ms"] != "(No calibration available)":
  167. atleast_two_frames = True
  168. return label_okay and atleast_two_frames
  169. # ______________________________________________________________________________________________________________________
  170. if __name__ == "__main__":
  171. # initialize a FlagsManager object with values specified above
  172. flags = FlagsManager()
  173. flags.update_flags({"STG_MotherOfAllFolders": STG_MotherOfAllFolders,
  174. "STG_OdorInfoPath" : STG_OdorInfoPath,
  175. "STG_Datapath" : STG_Datapath})
  176. # initialize importer
  177. importer_class = get_importer_class(LE_loadExp)
  178. importer = importer_class(default_values)
  179. # open a dialog for choosing raw data files
  180. # this returns a dictionary where keys are animal tags (STG_ReportTag) and
  181. # values are lists of associated raw data files
  182. animal_tag_raw_data_mapping = importer.ask_for_files(default_dir=flags["STG_Datapath"])
  183. # make sure some files were chosen
  184. assert len(animal_tag_raw_data_mapping) > 0, IOError("No files were chosen!")
  185. for animal_tag, raw_data_files in animal_tag_raw_data_mapping.items():
  186. # automatically parse metadata
  187. metadata_df = importer.import_metadata(raw_data_files=raw_data_files,
  188. measurement_filter=measurement_filter)
  189. # inform user if no usable measurements were found
  190. if metadata_df.shape[0] == 0:
  191. logging.info(f"No usable measurements was found among the files "
  192. f"chosen for the animal {animal_tag}. Not creating a list file")
  193. else:
  194. # create a new Measurement list object from parsed metadata
  195. measurement_list = MeasurementList.create_from_df(LE_loadExp=LE_loadExp,
  196. df=metadata_df)
  197. # apply custom modifications
  198. measurement_list.update_from_custom_func(custom_func=custom_func, animal_tag=animal_tag)
  199. # set anaylze to 0 if raw data files don't exist
  200. flags.update_flags({"STG_ReportTag": animal_tag})
  201. measurement_list.sanitize(flags=flags,
  202. data_file_extensions=importer.movie_data_extensions)
  203. # sort by time as in column "UTC"
  204. #sorted_df = df.sort_values(by=['Column_name'], ascending=True)
  205. # does not work if the list file already existed.
  206. measurement_list.measurement_list_df = measurement_list.measurement_list_df.sort_values(by=['UTC'], ascending=True)
  207. # construct the name of the output file
  208. #AskAjay - what I am writing seems crude to me (Giovanni Dec 21)
  209. #Ajay: out_file = f"{flags.get_lst_file_stem()}{measurement_output_extension}"
  210. singlefilein = pl.Path(raw_data_files[0])
  211. #singlefilein could be:
  212. #'/Users/galizia/Nextcloud/VTK_2021/Bente_Test_2021/01_DATA/190815_h2_El/B_1.tif'
  213. #output should be:
  214. #'/Users/galizia/Nextcloud/VTK_2021/Bente_Test_2021/02_ANALYSIS/190815_h2_El.lst.xlsx'
  215. out_file = pl.Path(singlefilein.parent.parent)
  216. out_file = pl.Path.joinpath(out_file, '02_LISTS' , singlefilein.parts[-1])
  217. out_file = pl.Path(out_file).with_suffix("") #remove '.lif' extension
  218. out_file = f"{out_file}{measurement_output_extension}"
  219. # write measurement file to list
  220. measurement_list.write_to_list_file(lst_fle=out_file, columns2write=default_values.keys(),
  221. overwrite_old_values=overwrite_old_values)