log2settings_VTK2021_old_for_reference.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. # -*- coding: utf-8 -*-
  2. """
  3. Program to read Till vision .log files
  4. and write .settings.csv files
  5. the program works like this (not all implemented yet):
  6. - set flag settings that are default values
  7. - read .log files and parse
  8. - extract information that is in there
  9. then, run Set_loca_Values_XXX, depending on reference_settings flag, e.g.
  10. - get FURA measurement partners
  11. - set stimulus times
  12. - etc (all the personal information)
  13. - extract information from names
  14. Since default flag settings are peculiar to every user,
  15. and extracting information from names is too,
  16. this program should be modified by everybody.
  17. Latest modification: 25.7.19 (Inga files)
  18. Output is .lst.xls, into folder /Lists
  19. """
  20. import scipy as sp
  21. import datetime
  22. import pandas as pd
  23. import os
  24. import tkinter as tk
  25. from tkinter.filedialog import askopenfilenames
  26. from tillvisionio.vws import VWSDataManager
  27. import pathlib as pl
  28. flag_OneDirectoryUp = True #if True, settings file is saved one directory up
  29. flag_intoListDirectory = True
  30. flag_writeMovies = False # not used any more
  31. flag_outextension = '.lst.xls'
  32. dbb_withDir = True # extract dbbXX.pst together with last directory, e.g. '190301_locust_ip16.pst/dbb6C.pst'
  33. ### set settings here!
  34. reference_settings = 'Temp' # 'Or42b_GC6' 'Or22a_GC6'
  35. ## for Jibin: set values in function Set_Jibin_Values(lst_frame)
  36. class OldFileHandlerBlank(object):
  37. def __init__(self):
  38. super().__init__()
  39. pass
  40. def backup(self):
  41. pass
  42. def write_old_values(self, df, columns):
  43. return df
  44. class OldFileHandler(OldFileHandlerBlank):
  45. def __init__(self, lst_file):
  46. super().__init__()
  47. self.lst_fle = lst_file
  48. self.old_lst_df = pd.read_excel(self.lst_fle).reset_index()
  49. if "index" in self.old_lst_df.columns:
  50. del self.old_lst_df["index"]
  51. self.backup_path = None
  52. def backup(self):
  53. lst_fle_path = pl.Path(self.lst_fle)
  54. self.backup_path = lst_fle_path.with_suffix(f".{datetime.datetime.now().strftime('%Y%m%d')}.xls")
  55. self.old_lst_df.to_excel(str(self.backup_path))
  56. print("Output file existed! Old file saved as ")
  57. print(str(self.backup_path))
  58. def write_old_values(self, df, columns: list):
  59. if "Measu" not in columns:
  60. columns.append("Measu")
  61. mask = self.old_lst_df["Label"].apply(lambda x: x in df["Label"].values)
  62. old_df_subset = self.old_lst_df.loc[mask, columns].set_index("Measu")
  63. df_temp = df.set_index("Measu")
  64. combined_df = df_temp.combine(old_df_subset, func=lambda s1, s2: s2, overwrite=False)
  65. return combined_df.reset_index()
  66. def get_old_file_handler(lst_file):
  67. if os.path.exists(lst_file):
  68. return OldFileHandler(lst_file)
  69. else:
  70. return OldFileHandlerBlank()
  71. def Set_local_Values_Sercan(lst_frame):
  72. lst_frame['StimON'] = 20
  73. lst_frame['StimLen'] = 1000
  74. lst_frame['Stim2ON'] = 60
  75. lst_frame['Stim2Len'] = 1000
  76. lst_frame['Comment'] = 'log2lst_Sercan_CalciumGreen'
  77. lst_frame['Line'] = 'locust'
  78. lst_frame['Age'] = '-1'
  79. lst_frame['Sex'] = 'o'
  80. #!!odor and concentration extracted from label!!
  81. return lst_frame
  82. def get_default_line():
  83. ################################################
  84. ### default values ########################
  85. ################################################
  86. # if there is a function such as Set_Jibin_Values, it will overwrite some of these
  87. default_comment = 'log2lst'
  88. default_line = 'locust'
  89. default_PxSzX = '1.5625' # um per pixel, for 50x air objective, measured by Hanna Schnell July 2017, with a binning of 8
  90. default_PxSzY = '1.5625'
  91. default_age = '-1'
  92. default_sex = 'o'
  93. default_odor = 'unknown'
  94. default_NOConc = '0'
  95. default_setting = 'setting'
  96. default_prefer = '1' # which area from KNIME to plot
  97. default_slvFlip = '0'
  98. ################################################
  99. # copy the default ones, some will be overwritten later
  100. lst_line = pd.Series()
  101. lst_line['Odour'] = default_odor
  102. lst_line['OConc'] = default_NOConc
  103. lst_line['setting'] = default_setting
  104. lst_line['Comment'] = default_comment
  105. lst_line['Line'] = default_line
  106. lst_line['PxSzX'] = default_PxSzX
  107. lst_line['PxSzY'] = default_PxSzY
  108. lst_line['Age'] = default_age
  109. lst_line['Sex'] = default_sex
  110. lst_line['Prefer'] = default_prefer
  111. lst_line["slvFlip"] = default_slvFlip
  112. return lst_line
  113. def parse_label(label):
  114. if reference_settings == "Temp":
  115. info = {}
  116. info["Odour"] = 'not set'
  117. info["OConc"] = 'not set'
  118. else:
  119. parts = label.split("_")
  120. info = {}
  121. info["Odour"] = parts[1]
  122. info["OConc"] = int(parts[2])
  123. return info
  124. def convert_vws_names_to_lst_names(vws_measurement_series, default_line):
  125. """
  126. Convert values from vws.log nomenclaure to .lst nomenclature
  127. :param vws_measurement_series: pandas.Series
  128. :return: pandas.series
  129. """
  130. lst_line = default_line.copy()
  131. lst_line['Animal'] = vws_measurement_series["Label"]
  132. lst_line['Measu'] = vws_measurement_series['index'] + 1
  133. lst_line['Label'] = vws_measurement_series['Label']
  134. lst_line['DBB1'] = vws_measurement_series["Location"]
  135. lst_line['Cycle'] = vws_measurement_series["dt"]
  136. lst_line['Lambda'] = vws_measurement_series['MonochromatorWL_nm']
  137. lst_line['UTC'] = vws_measurement_series['UTCTime']
  138. lst_line['StartTime'] = vws_measurement_series['StartTime']
  139. lst_line["Analyze"] = vws_measurement_series["Analyze"]
  140. #all others, just to not have empty columns
  141. lst_line['dbb2'] = 'none'
  142. lst_line['dbb3'] = 'none'
  143. lst_line['MTime'] = 0
  144. lst_line['Countl'] = 0
  145. lst_line['Control'] = 0
  146. lst_line['StimISI'] = 0
  147. lst_line['PhConc'] = 0
  148. lst_line['PhTime'] = 0
  149. lst_line['Pharma'] = "no_pharma"
  150. lst_line['PosZ'] = 0
  151. lst_line['ShiftX'] = 0
  152. lst_line['ShiftY'] = 0
  153. lst_line['Stim2OFF'] = -1
  154. lst_line['Stim2ON'] = -1
  155. lst_line['StimOFF'] = -1
  156. lst_line['StimON'] = -1
  157. label_info = parse_label(vws_measurement_series["Label"])
  158. lst_line["Odour"] = label_info["Odour"]
  159. lst_line["OConc"] = label_info["OConc"]
  160. return lst_line
  161. #####################################
  162. ## DEFINE REFERENCE ODOR AND TIME!!!!
  163. #####################################
  164. def log2settings(in_logFile, out_trunc, animal):
  165. """ converts a till photonics .vws.log into a lst.
  166. Function is not split into
  167. read and write because that is never needed
  168. Many columns may not be needed, but are still defined for compatibility fear
  169. """
  170. # # some constants and declarations
  171. # block_starts = []
  172. # block_ends = []
  173. # block_names = []
  174. # flag_oldvalues = False
  175. # in the future: replace with searching <end of info>
  176. lst_labels = ["Measu","Label","Odour","DBB1", "Cycle","MTime","OConc","Control",
  177. "StimON","StimOFF","Pharma","PhTime","PhConc","Comment","ShiftX","ShiftY","StimISI",
  178. "setting","dbb2","dbb3","PxSzX","PxSzY","PosZ","Lambda","Countl", "slvFlip", "Stim2ON","Stim2OFF",
  179. "Age", "Analyze","UTC", "Animal"]
  180. # last_time = 0
  181. # if a settings file already exists, make a backup
  182. # # read log and parse
  183. # with open(in_logFile, 'r') as fh:
  184. # lines = [line.strip() for line in fh.readlines()]
  185. # for i,line in enumerate(lines):
  186. # match = re.search('^\[(.*)\]',line) # looks for lines with []
  187. # if match:
  188. # block_starts.append(i)
  189. # block_names.append(match.group(1))
  190. # match = re.search('end of info',line) # looks for lines with end of info
  191. # if match:
  192. # block_ends.append(i)
  193. #
  194. # # make a list of those blocks that follow the naming conventions
  195. # valid_blocks = []
  196. # # remove blocks that have in the name "Snapshot" or "Delta"
  197. # # or that do not have any "_"
  198. # # 'Fluo340nm_00' would pass
  199. # for i,name in enumerate(block_names):
  200. # if name.count('Snapshot') > 0 \
  201. # or name.count('Delta') > 0 \
  202. # or name.count('_') < 1:
  203. # pass
  204. # else:
  205. # valid_blocks.append([i,block_starts[i],name,block_ends[i]])
  206. # # make a list for what is written in each block, with label. The log file has lines such as Date: 04/04/18
  207. # # and this will move into e.g. {'Date ': ' 04/04/18'}
  208. # Measurements = []
  209. # for i,block_info in enumerate(valid_blocks):
  210. # Measurements.append({'index':block_info[0],'label':block_info[2]})
  211. # block = lines[block_info[1]+1:block_info[3]] # all the lines that belong to this block
  212. # for line in block:
  213. # line_split = line.split(':')
  214. # if len(line_split) == 2:
  215. # key,value = line_split
  216. # if len(line_split) > 2:
  217. # key = line_split[0]
  218. # value = ':'.join(line_split[1:]).strip()
  219. # Measurements[i][key] = value
  220. #
  221. # lst_frame = pd.DataFrame(columns=lst_labels)
  222. # for Measurement in Measurements: # Measurement = Measurements[0]
  223. # lst_line = pd.DataFrame(columns=lst_labels) #one line for ease of writing
  224. #
  225. # # analyze label given in TILL for odor and concentration - not used in Jibin
  226. # # label_split = Measurement['label'].split('_')
  227. # # Odour = default_odor
  228. # # NOConc = default_NOConc
  229. # # setting = default_setting
  230. # # feedback = True
  231. # # if len(label_split) == 4: ## e.g. GC_06_ETBE_-2
  232. # # tmp, setting, Odour, NOConc = label_split
  233. # # setting = tmp + '_' + setting #reassemble
  234. # # feedback = False
  235. # # if len(label_split) == 3: ## e.g. GC06_ETBE_-2
  236. # # setting,Odour,NOConc = label_split
  237. # # feedback = False
  238. # # if len(label_split) == 2: ## eg. ETBE_-4
  239. # # Odour,NOConc = label_split
  240. # # feedback = False
  241. # # if feedback:
  242. # # print()
  243. # # print("names in TILL should be of the type xxx_odor_conc, e.g. GC00_ETBE_-2")
  244. # # print("or ETBE_-2 or GC_00_ETBE_-6")
  245. # # print()
  246. #
  247. # # time & analyze
  248. # try:
  249. # times = Measurement['timing [ms]'].strip()
  250. # times = sp.array(times.split(' '),dtype='float64')
  251. # # calculate frame rate as time of (last frame - first frame) / (frames-1)
  252. # dt = str((times[-1]-times[0])/(len(times)-1))
  253. # analyze = '1' # since there are at least two frames, and thus a time, I suppose it is worth analyzing
  254. # except:
  255. # dt = '-1'
  256. # analyze = '0'
  257. #
  258. # # location, check if pst, else -1
  259. # ext = os.path.splitext(Measurement['Location'])[1]
  260. # if ext != '.pst':
  261. # Location = '-1'
  262. # else:
  263. # #tanimal = os.path.splitext(os.path.splitext(os.path.basename(fname))[0])[0] # tanimal
  264. # dbb = os.path.splitext(Measurement['Location'].split('\\')[-1])[0] # dbb wo pst
  265. # # Location = '\\'.join([tanimal + '.pst',dbb]) #use this line to add the animal's .pst directory
  266. # Location = dbb #for \\Jibin\\JJ_01_C_F_180404a.pst\\dbbBA.pst, return 'dbbBA'
  267. # # "MTime","Control","StimON","StimOFF","Pharma","PhTime","PhConc","ShiftX","ShiftY","StimISI","dbb2","dbb3","PosZ","Countl","slvFlip","Stim2ON","Stim2OFF",]
  268. def measurement_filter(s):
  269. # remove blocks that have in the name "Snapshot" or "Delta"
  270. # or that do not have any "_"
  271. name = s["Label"]
  272. label_not_okay = name.count('Snapshot') > 0 or name.count('Delta') > 0 or name.count('_') < 1
  273. label_okay = not label_not_okay
  274. atleast_two_frames = False
  275. if type(s["Timing_ms"]) is str:
  276. if len(s["Timing_ms"].split(' ')) >= 2:
  277. atleast_two_frames = True
  278. return label_okay and atleast_two_frames
  279. def additional_cols(s):
  280. # time & analyze
  281. try:
  282. times = s['Timing_ms'].strip()
  283. times = sp.array(times.split(' '), dtype='float64')
  284. # calculate frame rate as time of (last frame - first frame) / (frames-1)
  285. dt = str((times[-1]-times[0])/(len(times)-1))
  286. analyze = '1' # since there are at least two frames, and thus a time, I suppose it is worth analyzing
  287. except Exception as e:
  288. dt = '-1'
  289. analyze = '0'
  290. # location, check if pst, else -1
  291. ext = os.path.splitext(s['Location'])[1]
  292. if ext != '.pst':
  293. Location = '-1'
  294. else:
  295. # for "....\\Jibin\\JJ_01_C_F_180404a.pst\\dbbBA.pst", return 'JJ_01_C_F_180404a.pst\\dbbBA.pst'
  296. dbb = pl.PureWindowsPath(s["Location"])
  297. if dbb_withDir:
  298. Location = str(pl.Path(dbb.parts[-2]) / dbb.stem)
  299. out_trunc_path = pl.Path(out_trunc)
  300. expected_path = out_trunc_path.parent.parent / "Data" / pl.Path(dbb.parts[-2]) / dbb.name
  301. if os.path.isfile(str(expected_path)):
  302. print(f"File found at expected location: {str(expected_path)}")
  303. else:
  304. print(f"File NOT found at expected location: {str(expected_path)}")
  305. else:
  306. Location = dbb.stem
  307. return {"dt": dt, "Analyze": analyze, "Location": Location}
  308. vws_manager = VWSDataManager(in_logFile)
  309. measurements = vws_manager.get_all_metadata(filter=measurement_filter, additional_cols_func=additional_cols)
  310. this_lst_frame = pd.DataFrame()
  311. for measurement_index, measurement_row in measurements.iterrows():
  312. lst_line = convert_vws_names_to_lst_names(vws_measurement_series=measurement_row,
  313. default_line=get_default_line())
  314. this_lst_frame = this_lst_frame.append(lst_line, ignore_index=True)
  315. labels_not_initialzed = set(lst_labels) - set(this_lst_frame)
  316. assert labels_not_initialzed == set(), f"Some required columns were not initialized:\n{labels_not_initialzed}"
  317. # # if .settings existed before, maybe some entries were already given
  318. # # this assumes that the rows are the same!
  319. # # better: find row where old_lst_df.label == label
  320. # if flag_oldvalues:
  321. # this_lst_df = old_lst_df.loc[old_lst_df.Label == lst_line['label'],:] #till uses "label", I use "Label"
  322. # lst_line.loc[index,'Line'] = this_lst_df.iloc[0]["Line"]
  323. # # alternative, in one code line: line = old_lst_df.loc[old_lst_df.Label == Label].iloc[0]['line']
  324. # lst_line.loc[index,'PxSzX'] = this_lst_df.iloc[0]["PxSzX"]
  325. # lst_line.loc[index,'PxSzY'] = this_lst_df.iloc[0]["PxSzY"]
  326. # lst_line.loc[index,'Age'] = this_lst_df.iloc[0]["Age"]
  327. # lst_line.loc[index,'Sex'] = this_lst_df.iloc[0]["Sex"]
  328. # lst_line.loc[index,'Prefer'] = this_lst_df.iloc[0]["Prefer"]
  329. # lst_line.loc[index,'Comment'] = this_lst_df.iloc[0]["Comment"]
  330. # lst_line.loc[index,'Analyze'] = this_lst_df.iloc[0]["Analyze"]
  331. # #get old entry, for age, sex, line, comment.PxSzY, PxSzX, odorshift
  332. #
  333. # # now collect all this into a dataframe, and move to next
  334. # lst_frame = lst_frame.append(lst_line)
  335. #
  336. return this_lst_frame
  337. # end function log2settings
  338. #now lst_frame contains all information
  339. #######################################################################
  340. # MAIN starts here
  341. #######################################################################
  342. # Choose raw files
  343. root = tk.Tk()
  344. root.withdraw() # so that windows closes after file chosen
  345. root.attributes('-topmost', True)
  346. filenames = askopenfilenames(
  347. parent=root,
  348. title='Select one or more Till .log files',
  349. filetypes=[('settings files', '*.log'), ('all files', '*')]
  350. ) # ask user to choose file
  351. # i = 0 #for debugging
  352. for i in range(len(filenames)):
  353. in_logFile = filenames[i]
  354. if flag_OneDirectoryUp:
  355. #slowly. e.g. c:/me/myself/I/animaldir/animal.vws.log
  356. lst_trunc = os.path.splitext(os.path.splitext(in_logFile)[0])[0] #remove two extensions
  357. fn_tmp = os.path.split(lst_trunc)[1] # name of the file (without extensions)
  358. dir_oneup = os.path.split(os.path.split(lst_trunc)[0])[0] #path to parent directory
  359. if flag_intoListDirectory:
  360. out_trunc = os.path.join(dir_oneup,'Lists', fn_tmp) #add filename to "Lists" folder in path
  361. else:
  362. out_trunc = os.path.join(dir_oneup, fn_tmp) #add filename
  363. #result: 'c:/me/myself/I/animal' extension, e.g. .lst.xls to be added
  364. else:
  365. out_trunc = os.path.splitext(os.path.splitext(in_logFile)[0])[0] # + '.settings' # removes both .vws and .log
  366. animal = os.path.basename(out_trunc)
  367. old_file_handler = get_old_file_handler(f"{out_trunc}{flag_outextension}")
  368. old_file_handler.backup()
  369. # read values from log file, into dataframe
  370. lst_frame = log2settings(in_logFile, out_trunc, animal)
  371. #'''''
  372. #Outside the measurements loop. Set values according to a list, depends on reference_settings
  373. #
  374. # modify this and calculate additional things, depending on reference_settings
  375. if reference_settings == 'Inga':
  376. lst_frame = Set_local_Values_Sercan(lst_frame)
  377. # lst_frame = CalcFuraFilms(lst_frame, out_trunc) # calculate and write a FURA film for each 340 in lst_frame
  378. lst_frame = old_file_handler.write_old_values(lst_frame, ["Line", "PxSzX", "PxSzY", "Age", "Sex", "Prefer",
  379. "Comment", "Analyze", "Odour", "OConc"])
  380. # write lst_frame
  381. # write dataframe version to .xls file
  382. print('writing to ',out_trunc,' and extension', flag_outextension)
  383. if flag_outextension == '.lst.xls':
  384. lst_frame.to_excel(out_trunc+'.lst.xls')
  385. if flag_outextension == '.lst':
  386. lst_frame.to_csv(out_trunc+'.lst',sep='\t')
  387. # think about creating response overview image, and single-area glodatamix - copy from log2settings_valid
  388. # write_martin_glodatamix(os.path.splitext(lst_fname)[0], lst_trunc, mask)