commit a70ba5003779a1fedd3331a65f813987eedbcf57 Author: tillraab Date: Thu Jun 2 13:51:21 2022 +0200 all files for init commit added diff --git a/LED_detect.py b/LED_detect.py new file mode 100644 index 0000000..9d16ed1 --- /dev/null +++ b/LED_detect.py @@ -0,0 +1,81 @@ +import os +import cv2 +import argparse +import numpy as np +import matplotlib.pyplot as plt + +def check_LED(cap, frame_count, x0, x1, y0, y1): + fig, ax = plt.subplots() + ax.plot([x0, x0], [y0, y1], 'r') + ax.plot([x1, x1], [y0, y1], 'r') + ax.plot([x0, x1], [y0, y0], 'r') + ax.plot([x0, x1], [y1, y1], 'r') + plt.ion() + + cap.set(cv2.CAP_PROP_POS_FRAMES, int(frame_count / 2)) + f = None + try: + for i in np.arange(int(frame_count / 2), frame_count): + ret, frame = cap.read() + if f == None: + f = ax.imshow(frame) + else: + f.set_data(frame) + + sum_frame = np.sum(frame, axis=2) + LED_v = np.mean(sum_frame[y0:y1, x0:x1]) + print('%.0f: %.1f \n' % (i, LED_v)) + plt.pause(0.001) + except KeyboardInterrupt: + plt.close() + quit() + quit() + +def main(file_path, check, x, y): + folder, filename = os.path.split(file_path) + cap = cv2.VideoCapture(file_path) + frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + + x0, x1 = x + y0, y1 = y + + if check: + check_LED(cap, frame_count, x0, x1, y0, y1) + + ############### + # cap.set(cv2.CAP_PROP_POS_FRAMES, int(frame_count / 2)) + # frame_count = 1000 + ######################## + + light_th = 100 + LED_val = np.zeros(frame_count) + print('Frame_count: %.0f' % frame_count) + for i in range(frame_count): + if i % 1000 == 0: + print('progress: %.1f' % ((i/frame_count)*100) + '%') + ret, frame = cap.read() + + sum_frame = np.sum(frame, axis=2) + LED_val[i] = np.mean(sum_frame[y0:y1, x0:x1]) + + np.save(os.path.join(folder, 'LED_val.npy'), LED_val) + + LED_frames = np.arange(len(LED_val)-1)[(LED_val[:-1] < light_th) & (LED_val[1:] > light_th)] + + np.save(os.path.join(folder, 'LED_frames.npy'), LED_frames) + fig, ax = plt.subplots() + ax.plot(np.arange(len(LED_val)), LED_val, color='k') + ax.plot(LED_frames, np.ones(len(LED_frames))*light_th, 'o', color='firebrick') + plt.show() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Detect frames of blinking LED in video recordings.') + parser.add_argument('file', type=str, help='video file to be analyzed') + parser.add_argument("-c", action="store_true", help="check if LED pos is correct") + parser.add_argument('-x', type=int, nargs=2, default=[1272, 1282], help='x-borders of LED detect area (in pixels)') + parser.add_argument('-y', type=int, nargs=2, default=[1500, 1516], help='y-borders of LED area (in pixels)') + args = parser.parse_args() + import glob + + main(args.file, args.c, args.x, args.y) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ded59c5 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# How to competition experiment +__Workflow__ (Python-scripts/applications): +1) wavetracker.trackingGUI +2) wavetracker.EODsorter +3) LED_detect.py +4) eval_LED.py +5) trial_analysis.py +6) event_videos.py (optional) + +## Raw data analysis using the wavetracker-modul + +### trackingGUI.py +__Frequency extraction and tracking__ +- open Raw-file (traces-grid1.raw) +- 'Spectrogram'-settings: + - overlap fraction: 0.8 + - frequency resolution: 1 +- check 'Auto-save'; press 'Run' + +__Fine spectrogram__ +- repeat steps above but press 'Calc. fine spec' instead of Run + - fine spec data saved in /home/"user"/analysis/"filename" + +### EODsorter.py + +- load dataset/folder +- correct tracked EOD traces +- fill EOD traces + - fine spec data needs to be manually added to the dataset-folder + +## Competition trial analysis + +### trail_analysis.py + +- Detection of winners, their EODf traces, rises, etc. Results stored in "data-path"/analysis. +- (optional) Meta.csv file in base-path of analyzed data. Creates entries for each + analyzed recording (index = file names) and stores Meta-data. Manual competation suggested. + +## Video analysis + +### LED_detect.py +- Detect blinking LED (emitted by electric recording setup). Used for synchronization. +- "-c" argument to identify correct detection area for LED +- '-x' (tuple) borders of LED detection window on X-axis (in pixels) +- '-y' (tuple) borders of LED detection window on Y-axis (in pixels) + +### eval_LED.py +- creates time vector to synchronize electric and video recording +- for each frame contains a time-point (in s) that corresponds to the electric recordings. + +## Rise videos (optional) +- generates for each detected rise a short video showing the fish's behavior around the rise event. +- sorted in 'base-path'/rise_video. \ No newline at end of file diff --git a/eval_LED.py b/eval_LED.py new file mode 100644 index 0000000..e990a3a --- /dev/null +++ b/eval_LED.py @@ -0,0 +1,59 @@ +import pandas as pd +import numpy as np +import sys +import os +import matplotlib.pyplot as plt +from IPython import embed +import cv2 +import glob + +def main(folder): + sr = 20000 + + video_path = glob.glob(os.path.join(folder, '2022*.mp4'))[0] + cap = cv2.VideoCapture(video_path) + frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + + times = np.load(os.path.join(folder, 'times.npy')) + LED_idx = pd.read_csv(os.path.join(folder, 'led_idxs.csv'), sep=',') + + led_idx = np.array(LED_idx).T[0] + led_frame = np.load(os.path.join(folder, 'LED_frames.npy')) + led_vals = np.load(os.path.join(folder, 'LED_val.npy')) + + led_idx_span = led_idx[-1] - led_idx[0] + led_frame_span = led_frame[-1] - led_frame[0] + + led_frame_to_idx = ((led_frame-led_frame[0]) / led_frame_span) * led_idx_span + led_idx[0] + + frame_idxs = np.arange(frame_count) + frame_times = (((frame_idxs - led_frame[0]) / led_frame_span) * led_idx_span + led_idx[0]) / sr + + if not os.path.exists(os.path.join(folder, 'analysis')): + os.mkdir(os.path.join(folder, 'analysis')) + np.save(os.path.join(folder, 'analysis', 'frame_times.npy'), frame_times) + + ######################################################################################## + fig, ax = plt.subplots() + ax.plot(led_vals) + ax.plot(led_frame, np.ones(len(led_frame))*100, '.', color='firebrick') + + ######################################################################################## + fig, ax = plt.subplots() + ax.plot(led_idx / sr, np.ones(len(led_idx)), '.', color='k') + ax.plot(led_frame_to_idx / sr, np.ones(len(led_frame_to_idx))+.1, '.', color='firebrick') + ax.plot([times[0], times[0]], [0.5, 1.5], 'k', lw=1) + ax.plot([times[-1], times[-1]], [0.5, 1.5], 'k', lw=1) + + ax.plot(frame_times, np.ones(len(frame_times))*0.5) + + ax.set_ylim(0, 2) + + plt.show() + + embed() + quit() + pass + +if __name__ == '__main__': + main(sys.argv[1]) \ No newline at end of file diff --git a/event_videos.py b/event_videos.py new file mode 100644 index 0000000..1466e03 --- /dev/null +++ b/event_videos.py @@ -0,0 +1,110 @@ +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +import os +import sys +import cv2 +import glob +import argparse +from IPython import embed +from tqdm import tqdm + +def main(folder, dt): + video_path = glob.glob(os.path.join(folder, '2022*.mp4'))[0] + create_video_path = os.path.join(folder, 'rise_video') + if not os.path.exists(create_video_path): + os.mkdir(create_video_path) + video = cv2.VideoCapture(video_path) # was 'cap' + + fish_freqs = np.load(os.path.join(folder, 'analysis', 'fish_freq_interp.npy')) + max_freq, min_freq = np.nanmax(fish_freqs), np.nanmin(fish_freqs) + rise_idx = np.load(os.path.join(folder, 'analysis', 'rise_idx.npy')) + frame_times = np.load(os.path.join(folder, 'analysis', 'frame_times.npy')) + times = np.load(os.path.join(folder, 'times.npy')) + ####################################### + for fish_nr in np.arange(2)[::-1]: + + for idx_oi in tqdm(np.array(rise_idx[fish_nr][~np.isnan(rise_idx[fish_nr])], dtype=int)): + # idx_oi = int(rise_idx[1][10]) + time_oi = times[idx_oi] + + # embed() + # quit() + + HH = int((time_oi / 3600) // 1) + MM = int((time_oi - HH * 3600) // 60) + SS = int(time_oi - HH * 3600 - MM * 60) + + frames_oi = np.arange(len(frame_times))[np.abs(frame_times - time_oi) <= dt] + idxs_oi = np.arange(len(times))[np.abs(times - time_oi) <= dt*3] + + fig = plt.figure(figsize=(20/2.54, 20/2.54)) + gs = gridspec.GridSpec(2, 1, left=0.1, bottom = 0.1, right=0.95, top=0.95, height_ratios=(4, 1)) + ax = [] + ax.append(fig.add_subplot(gs[0, 0])) + ax.append(fig.add_subplot(gs[1, 0])) + ax[1].plot(times[idxs_oi] - time_oi, fish_freqs[0][idxs_oi], marker='.', color='firebrick') + ax[1].plot(times[idxs_oi] - time_oi, fish_freqs[1][idxs_oi], marker='.', color='cornflowerblue') + ax[1].set_ylim(min_freq - (max_freq-min_freq)*0.25, max_freq + (max_freq-min_freq)*0.25) + ax[1].set_xlim(-dt*3, dt*3) + ax[0].set_xticks([]) + ax[0].set_yticks([]) + + ax[1].tick_params(labelsize=12) + ax[1].set_xlabel('time [s]', fontsize=14) + # plt.ion() + for i in tqdm(np.arange(len(frames_oi))): + video.set(cv2.CAP_PROP_POS_FRAMES, int(frames_oi[i])) + ret, frame = video.read() + + if i == 0: + img = ax[0].imshow(frame) + line, = ax[1].plot([frame_times[frames_oi[i]] - time_oi, frame_times[frames_oi[i]] - time_oi], + [min_freq - (max_freq-min_freq)*0.25, max_freq + (max_freq-min_freq)*0.25], + color='k', lw=1) + else: + img.set_data(frame) + line.set_data([frame_times[frames_oi[i]] - time_oi, frame_times[frames_oi[i]] - time_oi], + [min_freq - (max_freq-min_freq)*0.25, max_freq + (max_freq-min_freq)*0.25]) + + # label = ('rise_video/frame%4.f.jpg' % len(glob.glob('rise_video/*.jpg'))).replace(' ', '0') + label = (os.path.join(create_video_path, 'frame%4.f.jpg' % len(glob.glob(os.path.join(create_video_path, '*.jpg'))))).replace(' ', '0') + plt.savefig(label, dpi=300) + # plt.pause(0.001) + + win_lose_str = 'lose' if fish_nr == 1 else 'win' + # video_name = ("./rise_video/%s_%2.f:%2.f:%2.f.mp4" % (win_lose_str, HH, MM, SS)).replace(' ', '0') + # command = "ffmpeg -r 25 -i './rise_video/frame%4d.jpg' -vf 'pad=ceil(iw/2)*2:ceil(ih/2)*2' -vcodec libx264 -y -an" + + video_name = os.path.join(create_video_path, ("%s_%2.f:%2.f:%2.f.mp4" % (win_lose_str, HH, MM, SS)).replace(' ', '0')) + command1 = "ffmpeg -r 25 -i" + frames_path = '"%s"' % os.path.join(create_video_path, "frame%4d.jpg") + command2 = "-vf 'pad=ceil(iw/2)*2:ceil(ih/2)*2' -vcodec libx264 -y -an" + + os.system(' '.join([command1, frames_path, command2, video_name])) + os.system(' '.join(['rm', os.path.join(create_video_path, '*.jpg')])) + # os.system(' '.join([command, video_name])) + # os.system('rm ./rise_video/*.jpg') + plt.close() + embed() + quit() + + + ############################### + fig, ax = plt.subplots() + for i, c in enumerate(['firebrick', 'cornflowerblue']): + ax.plot(times, fish_freqs[i], marker='.', color=c) + r_idx = np.array(rise_idx[i][~np.isnan(rise_idx[i])], dtype=int) + ax.plot(times[r_idx], fish_freqs[i][r_idx], 'o', color='k') + pass + ############################## + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Generate videos around events.') + parser.add_argument('file', type=str, help='folder/dataset to generate videos from.') + parser.add_argument('-t', type=float, default=10, help='video duration before and after event.') + # parser.add_argument("-c", action="store_true", help="check if LED pos is correct") + # parser.add_argument('-x', type=int, nargs=2, default=[1272, 1282], help='x-borders of LED detect area (in pixels)') + # parser.add_argument('-y', type=int, nargs=2, default=[1500, 1516], help='y-borders of LED area (in pixels)') + args = parser.parse_args() + main(args.file, args.t) \ No newline at end of file diff --git a/trail_analysis.py b/trail_analysis.py new file mode 100644 index 0000000..5438d0f --- /dev/null +++ b/trail_analysis.py @@ -0,0 +1,260 @@ +import os +import sys +import argparse +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +import numpy as np +import pandas as pd + + +from thunderfish.eventdetection import detect_peaks +from IPython import embed + +class Trial(object): + def __init__(self, folder, base_path, meta, fish_count): + self._isValid = False + + self.base_path = base_path + self.folder = folder + + self.meta = meta + self.fish_count = fish_count + + self.light_sec = 3 * 60 * 60 + + self.ids = None + self.fish_freq = None + self.fish_freq_interp = None + self.fish_freq_val = None + + self.baseline_freq_times = None + self.baseline_freqs = None + + self.rise_idxs = [] + self.rise_size = [] + + self.fish_sign = None + self.fish_sign_interp = None + self.winner = None + self.loser = None + + if os.path.exists(os.path.join(self.base_path, self.folder, 'fund_v.npy')): + self.load() + + def __repr__(self): + return f'Trial(Date={self.folder}, winner={self.winner})' + # return self.folder + + def load(self): + self.fund_v = np.load(os.path.join(self.base_path, self.folder, 'fund_v.npy')) + self.idx_v = np.load(os.path.join(self.base_path, self.folder, 'idx_v.npy')) + self.times = np.load(os.path.join(self.base_path, self.folder, 'times.npy')) + self.ident_v = np.load(os.path.join(self.base_path, self.folder, 'ident_v.npy')) + self.sign_v = np.load(os.path.join(self.base_path, self.folder, 'sign_v.npy')) + + self.ids = np.unique(self.ident_v[~np.isnan(self.ident_v)]) + if len(self.ids) == self.fish_count: + self.isValid = True + + def reshape_and_interpolate(self): + self.fish_freq = np.full((self.fish_count, len(self.times)), np.nan) + self.fish_sign = np.full((self.fish_count, len(self.times), self.sign_v.shape[1]), np.nan) + + for enu, id in enumerate(self.ids): + self.fish_freq[enu][self.idx_v[self.ident_v == id]] = self.fund_v[self.ident_v == id] + self.fish_sign[enu][self.idx_v[self.ident_v == id]] = self.sign_v[self.ident_v == id] + + + self.fish_freq_interp = np.full(self.fish_freq.shape, np.nan) + self.fish_sign_interp = np.full(self.fish_sign.shape, np.nan) + + for enu, id in enumerate(self.ids): + i0, i1 = self.idx_v[self.ident_v == id][0], self.idx_v[self.ident_v == id][-1] + self.fish_freq_interp[enu, i0:i1+1] = np.interp(self.times[i0:i1+1], + self.times[self.idx_v[self.ident_v == id]], + self.fish_freq[enu][~np.isnan(self.fish_freq[enu])]) + + help_sign_v = list(map(lambda x: np.interp(self.times[i0:i1+1], self.times[self.idx_v[self.ident_v == id]], x), self.fish_sign[enu][~np.isnan(self.fish_freq[enu])].T)) + self.fish_sign_interp[enu, i0:i1+1] = np.array(help_sign_v).T + + def baseline_freq(self, bw = 300): + bins = np.arange(-bw / 2, self.times[-1] + bw / 2, bw) + self.baseline_freq_times = np.array(bins[:-1] + (bins[1] - bins[0])/2) + self.baseline_freqs = np.full((2, len(self.baseline_freq_times)), np.nan) + + for enu, id in enumerate(self.ids): + for i in range(len(bins) - 1): + Cf = self.fish_freq[enu][(self.times > bins[i]) & (self.times <= bins[i + 1])] + if len(Cf) == 0: + continue + else: + self.baseline_freqs[enu][i] = np.nanpercentile(Cf, 5) + + self.fish_freq_val = [np.nanmean(x[self.baseline_freq_times > self.light_sec]) for x in self.baseline_freqs] + + def winner_detection(self): + day_mask = self.times > self.light_sec + day_idxs = np.arange(len(self.times))[day_mask] + + shelter_power = np.empty((2, len(day_idxs))) + for enu, id in enumerate(self.ids): + shelter_power[enu] = self.fish_sign_interp[enu][day_idxs, -1] + + mean_shelter_power = np.nanmean(shelter_power, axis=1) + self.winner = 1 if mean_shelter_power[1] > mean_shelter_power[0] else 0 + self.loser = 0 if self.winner == 1 else 1 + + def rise_detection(self, rise_th): + def check_rises_size(peak): + peak_f = self.fish_freq[i][peak] + peak_t = self.times[peak] + + closest_baseline_idx = list(map(lambda x: np.argmin(np.abs(self.baseline_freq_times - x)), peak_t)) + closest_baseline_freq = self.baseline_freqs[i][closest_baseline_idx] + + rise_size = peak_f - closest_baseline_freq + + return rise_size + + for i in range(len(self.fish_freq)): + rise_peak_idx, trough = detect_peaks(self.fish_freq[i][~np.isnan(self.fish_freq[i])], rise_th) + non_nan_idx = np.arange(len(self.fish_freq[i]))[~np.isnan(self.fish_freq[i])] + rise_peak_idx, trough = non_nan_idx[rise_peak_idx], non_nan_idx[trough] + + rise_size = check_rises_size(rise_peak_idx) + + self.rise_idxs.append(rise_peak_idx[rise_size >= rise_th]) + self.rise_size.append(rise_size[rise_size >= rise_th]) + + def update_meta(self): + entries = self.meta.index.tolist() + if self. folder not in entries: + self.meta.loc[self.folder] = ['' for _ in self.meta.columns] + self.meta.loc[self.folder, 'Win_ID'] = self.ids[self.winner] + self.meta.loc[self.folder, 'Lose_ID'] = self.ids[self.loser] + + self.meta.loc[self.folder, 'Win_EODf'] = self.fish_freq_val[self.winner] + self.meta.loc[self.folder, 'Lose_EODf'] = self.fish_freq_val[self.loser] + + self.meta.loc[self.folder, 'Win_rise_c'] = len(self.rise_idxs[self.winner]) + self.meta.loc[self.folder, 'Lose_rise_c'] = len(self.rise_idxs[self.loser]) + + self.meta.loc[self.folder, 'light_sec'] = self.light_sec + + self.meta.to_csv(os.path.join(self.base_path, 'meta.csv'), sep =',') + + def ilustrate(self): + fig = plt.figure(figsize=(20/2.54, 12/2.54)) + gs = gridspec.GridSpec(1, 1, left = 0.1, bottom = 0.1, right = 0.95, top = 0.95) + ax = fig.add_subplot(gs[0, 0]) + + for enu, id in enumerate(self.ids): + c = 'firebrick' if self.winner == enu else 'forestgreen' + ax.plot(self.times, self.fish_freq[enu], marker='.', color=c, zorder=1) + ax.plot(self.times[np.isnan(self.fish_freq[enu])], self.fish_freq_interp[enu][np.isnan(self.fish_freq[enu])], '.', zorder=1, color=c, alpha=0.25) + ax.plot(self.baseline_freq_times, self.baseline_freqs[enu], '--', color='k', zorder=2) + + ax.plot(self.times[self.rise_idxs[enu]], self.fish_freq_interp[enu][self.rise_idxs[enu]], 'o', color='k') + + + win_str = '(W)' if self.winner == enu else '' + + ax.text(self.times[-1], self.fish_freq_val[enu]-10, '%.0f' % id + win_str, va ='center', ha='right') + + ax.set_xlim(0, self.times[-1]) + + freq_range = (np.nanmin(self.fish_freq), np.nanmax(self.fish_freq)) + ax.set_ylim(freq_range[0] - 20, freq_range[1] + 10) + plt.show() + + def save(self): + saveorder = -1 if self.winner == 1 else 1 + + np.save(os.path.join(self.base_path, self.folder, 'analysis', 'fish_freq.npy'), self.fish_freq[::saveorder]) + np.save(os.path.join(self.base_path, self.folder, 'analysis', 'fish_freq_interp.npy'), self.fish_freq_interp[::saveorder]) + + np.save(os.path.join(self.base_path, self.folder, 'analysis', 'baseline_freqs.npy'), self.baseline_freqs[::saveorder]) + np.save(os.path.join(self.base_path, self.folder, 'analysis', 'baseline_freq_times.npy'), self.baseline_freq_times[::saveorder]) + + help_lens = [len(x) for x in self.rise_idxs] + rise_idxs_s = np.full((self.fish_count, np.max(help_lens)), np.nan) + rise_size_s = np.full((self.fish_count, np.max(help_lens)), np.nan) + for i in range(self.fish_count): + rise_idxs_s[i][:len(self.rise_idxs[i])] = self.rise_idxs[i] + rise_size_s[i][:len(self.rise_size[i])] = self.rise_size[i] + np.save(os.path.join(self.base_path, self.folder, 'analysis', 'rise_idx.npy'), rise_idxs_s[::saveorder]) + np.save(os.path.join(self.base_path, self.folder, 'analysis', 'rise_size.npy'), rise_size_s[::saveorder]) + + @property + def isValid(self): + return self._isValid + + @isValid.setter + def isValid(self, value): + print('Trial (%s) is valid' % (self.folder)) + self._isValid = value + + def frame_to_idx(self, event_frames): + self.sr = 20000 + LED_idx = pd.read_csv(os.path.join(self.folder, 'led_idxs.csv'), sep=',') + + led_idx = np.array(LED_idx).T[0] + led_frame = np.load(os.path.join(self.folder, 'LED_frames.npy')) + + led_idx_span = led_idx[-1] - led_idx[0] + led_frame_span = led_frame[-1] - led_frame[0] + + frames_to_idx = ((event_frames - led_frame[0]) / led_frame_span) * led_idx_span + led_idx[0] + + event_times = frames_to_idx / self.sr + + return event_times + + +def main(): + parser = argparse.ArgumentParser(description='Evaluated electrode array recordings with multiple fish.') + parser.add_argument('-f', type=str, help='single recording analysis', default='') + # parser.add_argument("-c", action="store_true", help="check if LED pos is correct") + # parser.add_argument('-x', type=int, nargs=2, default=[1272, 1282], help='x-borders of LED detect area (in pixels)') + # parser.add_argument('-y', type=int, nargs=2, default=[1500, 1516], help='y-borders of LED area (in pixels)') + args = parser.parse_args() + + base_path = '/home/raab/data/2022_competition' + + if os.path.exists(os.path.join(base_path, 'meta.csv')): + meta = pd.read_csv(os.path.join(base_path, 'meta.csv'), sep=',', index_col=0) + else: + meta = None + + if args.f == '': + folders = os.listdir(base_path) + folders = [x for x in folders if not '.' in x] + else: + folders= [os.path.split(os.path.normpath(args.f))[-1]] + + trials = [] + for folder in folders: + trial = Trial(folder, base_path, meta, fish_count=2) + if not trial.isValid: + continue + + trial.reshape_and_interpolate() + trial.winner_detection() + trial.baseline_freq(bw=300) + + # ToDo: q10 corrected EODfs + + trial.rise_detection(rise_th=5) + + if meta is not None: + trial.update_meta() + + trial.save() + trial.ilustrate() + trials.append(trial) + + # meta.loc[folder, 'Fish1_ID'] = 1 + # meta.to_csv('') + +if __name__ == '__main__': + main() \ No newline at end of file