diff --git a/code/chirpdetection.py b/code/chirpdetection.py
index 415da4a..541015d 100755
--- a/code/chirpdetection.py
+++ b/code/chirpdetection.py
@@ -102,7 +102,7 @@ class ChirpPlotBuffer:
self.t0 = 0
fig = plt.figure(
- figsize=(14 / 2.54, 20 / 2.54)
+ figsize=(14 * ps.cm, 18 * ps.cm)
)
gs0 = gr.GridSpec(
@@ -133,8 +133,10 @@ class ChirpPlotBuffer:
data_oi,
self.data.raw_rate,
self.t0 - 5,
- [np.min(self.frequency) - 100, np.max(self.frequency) + 200]
+ [np.min(self.frequency) - 300, np.max(self.frequency) + 300]
)
+ ax0.set_ylim(np.min(self.frequency) - 100,
+ np.max(self.frequency) + 200)
for track_id in self.data.ids:
@@ -157,27 +159,35 @@ class ChirpPlotBuffer:
zorder=10, color=ps.gblue1)
else:
ax0.plot(t-self.t0_old, f, lw=lw,
- zorder=10, color=ps.gray, alpha=0.5)
-
- ax0.fill_between(
- np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
- q50 - self.config.minimal_bandwidth / 2,
- q50 + self.config.minimal_bandwidth / 2,
- color=ps.gblue1,
- lw=1,
- ls="dashed",
- alpha=0.5,
- )
+ zorder=10, color=ps.black)
+
+ # ax0.fill_between(
+ # np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
+ # q50 - self.config.minimal_bandwidth / 2,
+ # q50 + self.config.minimal_bandwidth / 2,
+ # color=ps.gblue1,
+ # lw=1,
+ # ls="dashed",
+ # alpha=0.5,
+ # )
+
+ # ax0.fill_between(
+ # np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
+ # search_lower,
+ # search_upper,
+ # color=ps.gblue2,
+ # lw=1,
+ # ls="dashed",
+ # alpha=0.5,
+ # )
+
+ ax0.axhline(q50 - self.config.minimal_bandwidth / 2,
+ color=ps.gblue1, lw=1, ls="dashed")
+ ax0.axhline(q50 + self.config.minimal_bandwidth / 2,
+ color=ps.gblue1, lw=1, ls="dashed")
+ ax0.axhline(search_lower, color=ps.gblue2, lw=1, ls="dashed")
+ ax0.axhline(search_upper, color=ps.gblue2, lw=1, ls="dashed")
- ax0.fill_between(
- np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
- search_lower,
- search_upper,
- color=ps.gblue2,
- lw=1,
- ls="dashed",
- alpha=0.5,
- )
# ax0.axhline(q50, spec_times[0], spec_times[-1],
# color=ps.gblue1, lw=2, ls="dashed")
# ax0.axhline(q50 + self.search_frequency,
@@ -187,7 +197,11 @@ class ChirpPlotBuffer:
if len(chirps) > 0:
for chirp in chirps:
ax0.scatter(
- chirp, np.median(self.frequency) + 150, c=ps.black, marker="v"
+ chirp, np.median(self.frequency), c=ps.red, marker=".",
+ edgecolors=ps.red,
+ facecolors=ps.red,
+ zorder=10,
+ s=70,
)
# plot waveform of filtered signal
@@ -207,25 +221,31 @@ class ChirpPlotBuffer:
c=ps.gblue3, lw=lw, label="baseline inst. freq.")
# plot filtered and rectified envelope
- ax4.plot(self.time, self.baseline_envelope, c=ps.gblue1, lw=lw)
+ ax4.plot(self.time, self.baseline_envelope *
+ waveform_scaler, c=ps.gblue1, lw=lw)
ax4.scatter(
(self.time)[self.baseline_peaks],
- self.baseline_envelope[self.baseline_peaks],
+ (self.baseline_envelope*waveform_scaler)[self.baseline_peaks],
edgecolors=ps.red,
+ facecolors=ps.red,
zorder=10,
- marker="o",
- facecolors="none",
+ marker=".",
+ s=70,
+ # facecolors="none",
)
# plot envelope of search signal
- ax5.plot(self.time, self.search_envelope, c=ps.gblue2, lw=lw)
+ ax5.plot(self.time, self.search_envelope *
+ waveform_scaler, c=ps.gblue2, lw=lw)
ax5.scatter(
(self.time)[self.search_peaks],
- self.search_envelope[self.search_peaks],
+ (self.search_envelope*waveform_scaler)[self.search_peaks],
edgecolors=ps.red,
+ facecolors=ps.red,
zorder=10,
- marker="o",
- facecolors="none",
+ marker=".",
+ s=70,
+ # facecolors="none",
)
# plot filtered instantaneous frequency
@@ -235,16 +255,20 @@ class ChirpPlotBuffer:
self.frequency_time[self.frequency_peaks],
self.frequency_filtered[self.frequency_peaks],
edgecolors=ps.red,
+ facecolors=ps.red,
zorder=10,
- marker="o",
- facecolors="none",
+ marker=".",
+ s=70,
+ # facecolors="none",
)
ax0.set_ylabel("frequency [Hz]")
- ax1.set_ylabel("a.u.")
- ax2.set_ylabel("a.u.")
+ ax1.set_ylabel(r"$\mu$V")
+ ax2.set_ylabel(r"$\mu$V")
ax3.set_ylabel("Hz")
- ax5.set_ylabel("a.u.")
+ ax4.set_ylabel(r"$\mu$V")
+ ax5.set_ylabel(r"$\mu$V")
+ ax6.set_ylabel("Hz")
ax6.set_xlabel("time [s]")
plt.setp(ax0.get_xticklabels(), visible=False)
@@ -323,7 +347,7 @@ def plot_spectrogram(
aspect="auto",
origin="lower",
interpolation="gaussian",
- alpha=0.6,
+ # alpha=0.6,
)
# axis.use_sticky_edges = False
return spec_times
@@ -628,7 +652,7 @@ def chirpdetection(datapath: str, plot: str, debug: str = 'false') -> None:
raw_time = np.arange(data.raw.shape[0]) / data.raw_rate
# good chirp times for data: 2022-06-02-10_00
- # window_start_index = (3 * 60 * 60 + 6 * 60 + 43.5 + 5) * data.raw_rate
+ # window_start_index = (3 * 60 * 60 + 6 * 60 + 43.5) * data.raw_rate
# window_duration_index = 60 * data.raw_rate
# t0 = 0
@@ -651,7 +675,7 @@ def chirpdetection(datapath: str, plot: str, debug: str = 'false') -> None:
multiwindow_chirps = []
multiwindow_ids = []
- for st, window_start_index in enumerate(window_start_indices):
+ for st, window_start_index in enumerate(window_start_indices[3175:]):
logger.info(f"Processing window {st+1} of {len(window_start_indices)}")
@@ -886,25 +910,25 @@ def chirpdetection(datapath: str, plot: str, debug: str = 'false') -> None:
# normalize all three feature arrays to the same range to make
# peak detection simpler
- baseline_envelope = minmaxnorm([baseline_envelope])[0]
- search_envelope = minmaxnorm([search_envelope])[0]
- baseline_frequency_filtered = minmaxnorm(
- [baseline_frequency_filtered]
- )[0]
+ # baseline_envelope = minmaxnorm([baseline_envelope])[0]
+ # search_envelope = minmaxnorm([search_envelope])[0]
+ # baseline_frequency_filtered = minmaxnorm(
+ # [baseline_frequency_filtered]
+ # )[0]
# PEAK DETECTION ----------------------------------------------
# detect peaks baseline_enelope
baseline_peak_indices, _ = find_peaks(
- baseline_envelope, prominence=config.prominence
+ baseline_envelope, prominence=config.baseline_prominence
)
# detect peaks search_envelope
search_peak_indices, _ = find_peaks(
- search_envelope, prominence=config.prominence
+ search_envelope, prominence=config.search_prominence
)
# detect peaks inst_freq_filtered
frequency_peak_indices, _ = find_peaks(
- baseline_frequency_filtered, prominence=config.prominence
+ baseline_frequency_filtered, prominence=config.frequency_prominence
)
# DETECT CHIRPS IN SEARCH WINDOW ------------------------------
@@ -1097,4 +1121,4 @@ if __name__ == "__main__":
datapath = "../data/2022-06-02-10_00/"
# datapath = "/home/weygoldt/Data/uni/efishdata/2016-colombia/fishgrid/2016-04-09-22_25/"
# datapath = "/home/weygoldt/Data/uni/chirpdetection/GP2023_chirp_detection/data/mount_data/2020-03-13-10_00/"
- chirpdetection(datapath, plot="show", debug="fish")
+ chirpdetection(datapath, plot="save", debug="false")
diff --git a/code/chirpdetector_conf.yml b/code/chirpdetector_conf.yml
index 4bc7d63..058448d 100755
--- a/code/chirpdetector_conf.yml
+++ b/code/chirpdetector_conf.yml
@@ -1,47 +1,41 @@
-# directory setup
-dataroot: "../data/"
-outputdir: "../output/"
+# Path setup ------------------------------------------------------------------
-# Duration and overlap of the analysis window in seconds
-window: 5
-overlap: 1
-edge: 0.25
+dataroot: "../data/" # path to data
+outputdir: "../output/" # path to save plots to
-# Number of electrodes to go over
-number_electrodes: 3
-minimum_electrodes: 2
+# Rolling window parameters ---------------------------------------------------
-# Search window bandwidth and minimal baseline bandwidth
-minimal_bandwidth: 20
+window: 5 # rolling window length in seconds
+overlap: 1 # window overlap in seconds
+edge: 0.25 # window edge cufoffs to mitigate filter edge effects
-# Instantaneous frequency smoothing usint a gaussian kernel of this width
-baseline_frequency_smoothing: 5
+# Electrode iteration parameters ----------------------------------------------
-# Baseline processing parameters
-baseline_envelope_cutoff: 25
-baseline_envelope_bandpass_lowf: 2
-baseline_envelope_bandpass_highf: 100
-# baseline_envelope_envelope_cutoff: 4
+number_electrodes: 2 # number of electrodes to go over
+minimum_electrodes: 1 # mimumun number of electrodes a chirp must be on
-# search envelope processing parameters
-search_envelope_cutoff: 10
+# Feature extraction parameters -----------------------------------------------
-# Instantaneous frequency bandpass filter cutoff frequencies
-# baseline_frequency_highpass_cutoff: 0.000005
-# baseline_frequency_envelope_cutoff: 0.000005
+search_df_lower: 20 # start searching this far above the baseline
+search_df_upper: 100 # stop searching this far above the baseline
+search_res: 1 # search window resolution
+default_search_freq: 60 # search here if no need for a search frequency
+minimal_bandwidth: 10 # minimal bandpass filter width for baseline
+search_bandwidth: 10 # minimal bandpass filter width for search frequency
+baseline_frequency_smoothing: 10 # instantaneous frequency smoothing
-# peak detecion parameters
-prominence: 0.7
+# Feature processing parameters -----------------------------------------------
-# search freq parameter
-search_df_lower: 20
-search_df_upper: 100
-search_res: 1
-search_bandwidth: 20
-default_search_freq: 60
-
-# Classify events as chirps if they are less than this time apart
-chirp_window_threshold: 0.015
+baseline_envelope_cutoff: 25 # envelope estimation cutoff
+baseline_envelope_bandpass_lowf: 2 # envelope badpass lower cutoff
+baseline_envelope_bandpass_highf: 100 # envelope bandbass higher cutoff
+search_envelope_cutoff: 10 # search envelope estimation cufoff
+# Peak detecion parameters ----------------------------------------------------
+baseline_prominence: 0.00005 # peak prominence threshold for baseline envelope
+search_prominence: 0.000004 # peak prominence threshold for search envelope
+frequency_prominence: 2 # peak prominence threshold for baseline freq
+# Classify events as chirps if they are less than this time apart
+chirp_window_threshold: 0.02
diff --git a/code/extract_chirps.py b/code/extract_chirps.py
index 4e229fa..5d5580e 100644
--- a/code/extract_chirps.py
+++ b/code/extract_chirps.py
@@ -4,11 +4,13 @@ import numpy as np
from chirpdetection import chirpdetection
from IPython import embed
+# check rec ../data/mount_data/2020-03-25-10_00/ starting at 3175
+
def main(datapaths):
for path in datapaths:
- chirpdetection(path, plot='show', debug='electrode')
+ chirpdetection(path, plot='show')
if __name__ == '__main__':
@@ -43,6 +45,7 @@ if __name__ == '__main__':
recs = pd.DataFrame(columns=['recording'], data=valid_datasets)
recs.to_csv('../recs.csv', index=False)
- # main(datapaths)
+ datapaths = ['../data/mount_data/2020-03-25-10_00/']
+ main(datapaths)
# window 1524 + 244 in dataset index 4 is nice example
diff --git a/code/modules/behaviour_handling.py b/code/modules/behaviour_handling.py
new file mode 100644
index 0000000..90a18ab
--- /dev/null
+++ b/code/modules/behaviour_handling.py
@@ -0,0 +1,99 @@
+import numpy as np
+
+import os
+
+import numpy as np
+from IPython import embed
+
+
+from pandas import read_csv
+from modules.logger import makeLogger
+
+
+logger = makeLogger(__name__)
+
+
+class Behavior:
+ """Load behavior data from csv file as class attributes
+ Attributes
+ ----------
+ behavior: 0: chasing onset, 1: chasing offset, 2: physical contact
+ behavior_type:
+ behavioral_category:
+ comment_start:
+ comment_stop:
+ dataframe: pandas dataframe with all the data
+ duration_s:
+ media_file:
+ observation_date:
+ observation_id:
+ start_s: start time of the event in seconds
+ stop_s: stop time of the event in seconds
+ total_length:
+ """
+
+ def __init__(self, folder_path: str) -> None:
+
+ LED_on_time_BORIS = np.load(os.path.join(folder_path, 'LED_on_time.npy'), allow_pickle=True)
+
+ csv_filename = [f for f in os.listdir(folder_path) if f.endswith('.csv')][0]
+ logger.info(f'CSV file: {csv_filename}')
+ self.dataframe = read_csv(os.path.join(folder_path, csv_filename))
+
+ self.chirps = np.load(os.path.join(folder_path, 'chirps.npy'), allow_pickle=True)
+ self.chirps_ids = np.load(os.path.join(folder_path, 'chirp_ids.npy'), allow_pickle=True)
+
+ self.ident = np.load(os.path.join(folder_path, 'ident_v.npy'), allow_pickle=True)
+ self.idx = np.load(os.path.join(folder_path, 'idx_v.npy'), allow_pickle=True)
+ self.freq = np.load(os.path.join(folder_path, 'fund_v.npy'), allow_pickle=True)
+ self.time = np.load(os.path.join(folder_path, "times.npy"), allow_pickle=True)
+ self.spec = np.load(os.path.join(folder_path, "spec.npy"), allow_pickle=True)
+
+ for k, key in enumerate(self.dataframe.keys()):
+ key = key.lower()
+ if ' ' in key:
+ key = key.replace(' ', '_')
+ if '(' in key:
+ key = key.replace('(', '')
+ key = key.replace(')', '')
+ setattr(self, key, np.array(self.dataframe[self.dataframe.keys()[k]]))
+
+ last_LED_t_BORIS = LED_on_time_BORIS[-1]
+ real_time_range = self.time[-1] - self.time[0]
+ factor = 1.034141
+ shift = last_LED_t_BORIS - real_time_range * factor
+ self.start_s = (self.start_s - shift) / factor
+ self.stop_s = (self.stop_s - shift) / factor
+
+
+def correct_chasing_events(
+ category: np.ndarray,
+ timestamps: np.ndarray
+ ) -> tuple[np.ndarray, np.ndarray]:
+
+ onset_ids = np.arange(
+ len(category))[category == 0]
+ offset_ids = np.arange(
+ len(category))[category == 1]
+
+ woring_bh = np.arange(len(category))[category!=2][:-1][np.diff(category[category!=2])==0]
+ if onset_ids[0] > offset_ids[0]:
+ offset_ids = np.delete(offset_ids, 0)
+ help_index = offset_ids[0]
+ woring_bh = np.append(woring_bh, help_index)
+
+ category = np.delete(category, woring_bh)
+ timestamps = np.delete(timestamps, woring_bh)
+
+ # Check whether on- or offset is longer and calculate length difference
+ if len(onset_ids) > len(offset_ids):
+ len_diff = len(onset_ids) - len(offset_ids)
+ logger.info(f'Onsets are greater than offsets by {len_diff}')
+ elif len(onset_ids) < len(offset_ids):
+ len_diff = len(offset_ids) - len(onset_ids)
+ logger.info(f'Offsets are greater than onsets by {len_diff}')
+ elif len(onset_ids) == len(offset_ids):
+ logger.info('Chasing events are equal')
+
+
+ return category, timestamps
\ No newline at end of file
diff --git a/code/modules/filehandling.py b/code/modules/filehandling.py
index 334aefa..c3c71f2 100644
--- a/code/modules/filehandling.py
+++ b/code/modules/filehandling.py
@@ -3,6 +3,7 @@ import os
import yaml
import numpy as np
from thunderfish.dataloader import DataLoader
+import matplotlib.pyplot as plt
class ConfLoader:
diff --git a/code/modules/plotstyle.py b/code/modules/plotstyle.py
index 2325f62..b4a8a41 100644
--- a/code/modules/plotstyle.py
+++ b/code/modules/plotstyle.py
@@ -23,16 +23,16 @@ def PlotStyle() -> None:
sky = "#89dceb"
teal = "#94e2d5"
green = "#a6e3a1"
- yellow = "#f9e2af"
- orange = "#fab387"
- maroon = "#eba0ac"
- red = "#f38ba8"
- purple = "#cba6f7"
- pink = "#f5c2e7"
+ yellow = "#f9d67f"
+ orange = "#faa472"
+ maroon = "#eb8486"
+ red = "#f37588"
+ purple = "#d89bf7"
+ pink = "#f59edb"
lavender = "#b4befe"
- gblue1 = "#8cb8ff"
- gblue2 = "#7cdcdc"
- gblue3 = "#82e896"
+ gblue1 = "#89b4fa"
+ gblue2 = "#89dceb"
+ gblue3 = "#a6e3a1"
@classmethod
def lims(cls, track1, track2):
@@ -229,7 +229,7 @@ def PlotStyle() -> None:
plt.rc("legend", fontsize=SMALL_SIZE) # legend fontsize
plt.rc("figure", titlesize=BIGGER_SIZE) # fontsize of the figure title
- plt.rcParams["image.cmap"] = 'cmo.haline'
+ plt.rcParams["image.cmap"] = "cmo.haline"
plt.rcParams["axes.xmargin"] = 0.05
plt.rcParams["axes.ymargin"] = 0.1
plt.rcParams["axes.titlelocation"] = "left"
@@ -261,31 +261,33 @@ def PlotStyle() -> None:
# plt.rcParams["axes.grid"] = True # display grid or not
# plt.rcParams["axes.grid.axis"] = "y" # which axis the grid is applied to
plt.rcParams["axes.labelcolor"] = white
- plt.rcParams["axes.axisbelow"] = True # draw axis gridlines and ticks:
+ plt.rcParams["axes.axisbelow"] = True # draw axis gridlines and ticks:
plt.rcParams["axes.spines.left"] = True # display axis spines
plt.rcParams["axes.spines.bottom"] = True
plt.rcParams["axes.spines.top"] = False
plt.rcParams["axes.spines.right"] = False
plt.rcParams["axes.prop_cycle"] = cycler(
- 'color', [
- '#b4befe',
- '#89b4fa',
- '#74c7ec',
- '#89dceb',
- '#94e2d5',
- '#a6e3a1',
- '#f9e2af',
- '#fab387',
- '#eba0ac',
- '#f38ba8',
- '#cba6f7',
- '#f5c2e7',
- ])
+ "color",
+ [
+ "#b4befe",
+ "#89b4fa",
+ "#74c7ec",
+ "#89dceb",
+ "#94e2d5",
+ "#a6e3a1",
+ "#f9e2af",
+ "#fab387",
+ "#eba0ac",
+ "#f38ba8",
+ "#cba6f7",
+ "#f5c2e7",
+ ],
+ )
plt.rcParams["xtick.color"] = gray # color of the ticks
plt.rcParams["ytick.color"] = gray # color of the ticks
plt.rcParams["grid.color"] = dark_gray # grid color
- plt.rcParams["figure.facecolor"] = black # figure face color
- plt.rcParams["figure.edgecolor"] = black # figure edge color
+ plt.rcParams["figure.facecolor"] = black # figure face color
+ plt.rcParams["figure.edgecolor"] = black # figure edge color
plt.rcParams["savefig.facecolor"] = black # figure face color when saving
return style
@@ -295,12 +297,11 @@ if __name__ == "__main__":
s = PlotStyle()
- import matplotlib.pyplot as plt
+ import matplotlib.cbook as cbook
import matplotlib.cm as cm
import matplotlib.pyplot as plt
- import matplotlib.cbook as cbook
- from matplotlib.path import Path
from matplotlib.patches import PathPatch
+ from matplotlib.path import Path
# Fixing random state for reproducibility
np.random.seed(19680801)
@@ -308,14 +309,20 @@ if __name__ == "__main__":
delta = 0.025
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
- Z1 = np.exp(-X**2 - Y**2)
- Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
+ Z1 = np.exp(-(X**2) - Y**2)
+ Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
Z = (Z1 - Z2) * 2
fig1, ax = plt.subplots()
- im = ax.imshow(Z, interpolation='bilinear', cmap=cm.RdYlGn,
- origin='lower', extent=[-3, 3, -3, 3],
- vmax=abs(Z).max(), vmin=-abs(Z).max())
+ im = ax.imshow(
+ Z,
+ interpolation="bilinear",
+ cmap=cm.RdYlGn,
+ origin="lower",
+ extent=[-3, 3, -3, 3],
+ vmax=abs(Z).max(),
+ vmin=-abs(Z).max(),
+ )
plt.show()
@@ -328,22 +335,21 @@ if __name__ == "__main__":
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
# plot violin plot
- axs[0].violinplot(all_data,
- showmeans=False,
- showmedians=True)
- axs[0].set_title('Violin plot')
+ axs[0].violinplot(all_data, showmeans=False, showmedians=True)
+ axs[0].set_title("Violin plot")
# plot box plot
axs[1].boxplot(all_data)
- axs[1].set_title('Box plot')
+ axs[1].set_title("Box plot")
# adding horizontal grid lines
for ax in axs:
ax.yaxis.grid(True)
- ax.set_xticks([y + 1 for y in range(len(all_data))],
- labels=['x1', 'x2', 'x3', 'x4'])
- ax.set_xlabel('Four separate samples')
- ax.set_ylabel('Observed values')
+ ax.set_xticks(
+ [y + 1 for y in range(len(all_data))], labels=["x1", "x2", "x3", "x4"]
+ )
+ ax.set_xlabel("Four separate samples")
+ ax.set_ylabel("Observed values")
plt.show()
@@ -355,24 +361,42 @@ if __name__ == "__main__":
theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
radii = 10 * np.random.rand(N)
width = np.pi / 4 * np.random.rand(N)
- colors = cmo.cm.haline(radii / 10.)
+ colors = cmo.cm.haline(radii / 10.0)
- ax = plt.subplot(projection='polar')
+ ax = plt.subplot(projection="polar")
ax.bar(theta, radii, width=width, bottom=0.0, color=colors, alpha=0.5)
plt.show()
- methods = [None, 'none', 'nearest', 'bilinear', 'bicubic', 'spline16',
- 'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
- 'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']
+ methods = [
+ None,
+ "none",
+ "nearest",
+ "bilinear",
+ "bicubic",
+ "spline16",
+ "spline36",
+ "hanning",
+ "hamming",
+ "hermite",
+ "kaiser",
+ "quadric",
+ "catrom",
+ "gaussian",
+ "bessel",
+ "mitchell",
+ "sinc",
+ "lanczos",
+ ]
# Fixing random state for reproducibility
np.random.seed(19680801)
grid = np.random.rand(4, 4)
- fig, axs = plt.subplots(nrows=3, ncols=6, figsize=(9, 6),
- subplot_kw={'xticks': [], 'yticks': []})
+ fig, axs = plt.subplots(
+ nrows=3, ncols=6, figsize=(9, 6), subplot_kw={"xticks": [], "yticks": []}
+ )
for ax, interp_method in zip(axs.flat, methods):
ax.imshow(grid, interpolation=interp_method)
diff --git a/code/plot_chirp_bodylegth.py b/code/plot_chirp_bodylegth.py
new file mode 100644
index 0000000..6d5c782
--- /dev/null
+++ b/code/plot_chirp_bodylegth.py
@@ -0,0 +1,160 @@
+import numpy as np
+
+import os
+
+import numpy as np
+import matplotlib.pyplot as plt
+from thunderfish.powerspectrum import decibel
+
+from IPython import embed
+from pandas import read_csv
+from modules.logger import makeLogger
+from modules.plotstyle import PlotStyle
+from modules.behaviour_handling import Behavior, correct_chasing_events
+
+ps = PlotStyle()
+
+logger = makeLogger(__name__)
+
+
+def main(datapath: str):
+
+ foldernames = [
+ datapath + x + '/' for x in os.listdir(datapath) if os.path.isdir(datapath+x)]
+ path_order_meta = (
+ '/').join(foldernames[0].split('/')[:-2]) + '/order_meta.csv'
+ order_meta_df = read_csv(path_order_meta)
+ order_meta_df['recording'] = order_meta_df['recording'].str[1:-1]
+ path_id_meta = (
+ '/').join(foldernames[0].split('/')[:-2]) + '/id_meta.csv'
+ id_meta_df = read_csv(path_id_meta)
+
+ chirps_winner = []
+ size_diff = []
+ chirps_diff = []
+ chirps_loser = []
+ freq_diff = []
+
+
+ for foldername in foldernames:
+ # behabvior is pandas dataframe with all the data
+ if foldername == '../data/mount_data/2020-05-12-10_00/':
+ continue
+ bh = Behavior(foldername)
+ # chirps are not sorted in time (presumably due to prior groupings)
+ # get and sort chirps and corresponding fish_ids of the chirps
+ category = bh.behavior
+ timestamps = bh.start_s
+ # Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
+ # Get rid of tracking faults (two onsets or two offsets after another)
+ category, timestamps = correct_chasing_events(category, timestamps)
+
+ folder_name = foldername.split('/')[-2]
+ winner_row = order_meta_df[order_meta_df['recording'] == folder_name]
+ winner = winner_row['winner'].values[0].astype(int)
+ winner_fish1 = winner_row['fish1'].values[0].astype(int)
+ winner_fish2 = winner_row['fish2'].values[0].astype(int)
+
+ groub = winner_row['group'].values[0].astype(int)
+ size_rows = id_meta_df[id_meta_df['group'] == groub]
+
+
+ if winner == winner_fish1:
+ winner_fish_id = winner_row['rec_id1'].values[0]
+ loser_fish_id = winner_row['rec_id2'].values[0]
+
+ size_winners = []
+ for l in ['l1', 'l2', 'l3']:
+ size_winner = size_rows[size_rows['fish']== winner_fish1][l].values[0]
+ size_winners.append(size_winner)
+ mean_size_winner = np.nanmean(size_winners)
+
+
+ size_losers = []
+ for l in ['l1', 'l2', 'l3']:
+ size_loser = size_rows[size_rows['fish']== winner_fish2][l].values[0]
+ size_losers.append(size_loser)
+ mean_size_loser = np.nanmean(size_losers)
+
+ size_diff.append(mean_size_winner - mean_size_loser)
+
+ elif winner == winner_fish2:
+ winner_fish_id = winner_row['rec_id2'].values[0]
+ loser_fish_id = winner_row['rec_id1'].values[0]
+
+ size_winners = []
+ for l in ['l1', 'l2', 'l3']:
+ size_winner = size_rows[size_rows['fish']== winner_fish2][l].values[0]
+ size_winners.append(size_winner)
+ mean_size_winner = np.nanmean(size_winners)
+
+ size_losers = []
+ for l in ['l1', 'l2', 'l3']:
+ size_loser = size_rows[size_rows['fish']== winner_fish1][l].values[0]
+ size_losers.append(size_loser)
+ mean_size_loser = np.nanmean(size_losers)
+
+ size_diff.append(mean_size_winner - mean_size_loser)
+ else:
+ continue
+
+ print(foldername)
+ all_fish_ids = np.unique(bh.chirps_ids)
+ chirp_winner = len(bh.chirps[bh.chirps_ids == winner_fish_id])
+ chirp_loser = len(bh.chirps[bh.chirps_ids == loser_fish_id])
+
+ freq_winner = np.nanmedian(bh.freq[bh.ident==winner_fish_id])
+ freq_loser = np.nanmedian(bh.freq[bh.ident==loser_fish_id])
+
+
+ chirps_winner.append(chirp_winner)
+ chirps_loser.append(chirp_loser)
+
+ chirps_diff.append(chirp_winner - chirp_loser)
+ freq_diff.append(freq_winner - freq_loser)
+
+ fish1_id = all_fish_ids[0]
+ fish2_id = all_fish_ids[1]
+ print(winner_fish_id)
+ print(all_fish_ids)
+
+ fig, (ax1, ax2, ax3) = plt.subplots(1,3, figsize=(10,5))
+ scatterwinner = 1.15
+ scatterloser = 1.85
+ bplot1 = ax1.boxplot(chirps_winner, positions=[
+ 1], showfliers=False, patch_artist=True)
+ bplot2 = ax1.boxplot(chirps_loser, positions=[
+ 2], showfliers=False, patch_artist=True)
+ ax1.scatter(np.ones(len(chirps_winner))*scatterwinner, chirps_winner, color='r')
+ ax1.scatter(np.ones(len(chirps_loser))*scatterloser, chirps_loser, color='r')
+ ax1.set_xticklabels(['winner', 'loser'])
+ ax1.text(0.9, 0.9, f'n = {len(chirps_winner)}', transform=ax1.transAxes, color= ps.white)
+
+ for w, l in zip(chirps_winner, chirps_loser):
+ ax1.plot([scatterwinner, scatterloser], [w, l], color='r', alpha=0.5, linewidth=0.5)
+
+ colors1 = ps.red
+ ps.set_boxplot_color(bplot1, colors1)
+ colors1 = ps.orange
+ ps.set_boxplot_color(bplot2, colors1)
+ ax1.set_ylabel('Chirpscounts [n]')
+
+ ax2.scatter(size_diff, chirps_diff, color='r')
+ ax2.set_xlabel('Size difference [mm]')
+ ax2.set_ylabel('Chirps difference [n]')
+
+ ax3.scatter(freq_diff, chirps_diff, color='r')
+ ax3.set_xlabel('Frequency difference [Hz]')
+ ax3.set_yticklabels([])
+ ax3.set
+
+ plt.savefig('../poster/figs/chirps_winner_loser.pdf')
+ plt.show()
+
+
+if __name__ == '__main__':
+
+ # Path to the data
+ datapath = '../data/mount_data/'
+
+ main(datapath)
diff --git a/code/plot_event_timeline.py b/code/plot_event_timeline.py
index 6c984be..96f4e31 100644
--- a/code/plot_event_timeline.py
+++ b/code/plot_event_timeline.py
@@ -10,194 +10,102 @@ from IPython import embed
from pandas import read_csv
from modules.logger import makeLogger
from modules.plotstyle import PlotStyle
+from modules.behaviour_handling import Behavior, correct_chasing_events
ps = PlotStyle()
logger = makeLogger(__name__)
-class Behavior:
- """Load behavior data from csv file as class attributes
- Attributes
- ----------
- behavior: 0: chasing onset, 1: chasing offset, 2: physical contact
- behavior_type:
- behavioral_category:
- comment_start:
- comment_stop:
- dataframe: pandas dataframe with all the data
- duration_s:
- media_file:
- observation_date:
- observation_id:
- start_s: start time of the event in seconds
- stop_s: stop time of the event in seconds
- total_length:
- """
-
- def __init__(self, folder_path: str) -> None:
-
-
- LED_on_time_BORIS = np.load(os.path.join(folder_path, 'LED_on_time.npy'), allow_pickle=True)
-
- csv_filename = [f for f in os.listdir(folder_path) if f.endswith('.csv')][0]
- logger.info(f'CSV file: {csv_filename}')
- self.dataframe = read_csv(os.path.join(folder_path, csv_filename))
-
- self.chirps = np.load(os.path.join(folder_path, 'chirps.npy'), allow_pickle=True)
- self.chirps_ids = np.load(os.path.join(folder_path, 'chirps_ids.npy'), allow_pickle=True)
-
- self.ident = np.load(os.path.join(folder_path, 'ident_v.npy'), allow_pickle=True)
- self.idx = np.load(os.path.join(folder_path, 'idx_v.npy'), allow_pickle=True)
- self.freq = np.load(os.path.join(folder_path, 'fund_v.npy'), allow_pickle=True)
- self.time = np.load(os.path.join(folder_path, "times.npy"), allow_pickle=True)
- self.spec = np.load(os.path.join(folder_path, "spec.npy"), allow_pickle=True)
-
- for k, key in enumerate(self.dataframe.keys()):
- key = key.lower()
- if ' ' in key:
- key = key.replace(' ', '_')
- if '(' in key:
- key = key.replace('(', '')
- key = key.replace(')', '')
- setattr(self, key, np.array(self.dataframe[self.dataframe.keys()[k]]))
-
- last_LED_t_BORIS = LED_on_time_BORIS[-1]
- real_time_range = self.time[-1] - self.time[0]
- factor = 1.034141
- shift = last_LED_t_BORIS - real_time_range * factor
- self.start_s = (self.start_s - shift) / factor
- self.stop_s = (self.stop_s - shift) / factor
-
-def correct_chasing_events(
- category: np.ndarray,
- timestamps: np.ndarray
- ) -> tuple[np.ndarray, np.ndarray]:
-
- onset_ids = np.arange(
- len(category))[category == 0]
- offset_ids = np.arange(
- len(category))[category == 1]
-
- # Check whether on- or offset is longer and calculate length difference
- if len(onset_ids) > len(offset_ids):
- len_diff = len(onset_ids) - len(offset_ids)
- longer_array = onset_ids
- shorter_array = offset_ids
- logger.info(f'Onsets are greater than offsets by {len_diff}')
- elif len(onset_ids) < len(offset_ids):
- len_diff = len(offset_ids) - len(onset_ids)
- longer_array = offset_ids
- shorter_array = onset_ids
- logger.info(f'Offsets are greater than offsets by {len_diff}')
- elif len(onset_ids) == len(offset_ids):
- logger.info('Chasing events are equal')
- return category, timestamps
-
-
- # Correct the wrong chasing events; delete double events
- wrong_ids = []
- for i in range(len(longer_array)-(len_diff+1)):
- if (shorter_array[i] > longer_array[i]) & (shorter_array[i] < longer_array[i+1]):
- pass
- else:
- wrong_ids.append(longer_array[i])
- longer_array = np.delete(longer_array, i)
-
- category = np.delete(
- category, wrong_ids)
- timestamps = np.delete(
- timestamps, wrong_ids)
- return category, timestamps
-
-
-
def main(datapath: str):
- # behabvior is pandas dataframe with all the data
- bh = Behavior(datapath)
- # chirps are not sorted in time (presumably due to prior groupings)
- # get and sort chirps and corresponding fish_ids of the chirps
- chirps = bh.chirps[np.argsort(bh.chirps)]
- chirps_fish_ids = bh.chirps_ids[np.argsort(bh.chirps)]
- category = bh.behavior
- timestamps = bh.start_s
- # Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
- # Get rid of tracking faults (two onsets or two offsets after another)
- category, timestamps = correct_chasing_events(category, timestamps)
-
- # split categories
- chasing_onset = (timestamps[category == 0]/ 60) /60
- chasing_offset = (timestamps[category == 1]/ 60) /60
- physical_contact = (timestamps[category == 2] / 60) /60
-
- all_fish_ids = np.unique(chirps_fish_ids)
- fish1_id = all_fish_ids[0]
- fish2_id = all_fish_ids[1]
- # Associate chirps to inidividual fish
- fish1 = (chirps[chirps_fish_ids == fish1_id] / 60) /60
- fish2 = (chirps[chirps_fish_ids == fish2_id] / 60) /60
- fish1_color = ps.red
- fish2_color = ps.orange
-
- fig, ax = plt.subplots(4, 1, figsize=(10, 5), height_ratios=[0.5, 0.5, 0.5, 6], sharex=True)
- # marker size
- s = 200
- ax[0].scatter(physical_contact, np.ones(len(physical_contact)), color='firebrick', marker='|', s=s)
- ax[1].scatter(chasing_onset, np.ones(len(chasing_onset)), color='green', marker='|', s=s )
- ax[2].scatter(fish1, np.ones(len(fish1))-0.25, color=fish1_color, marker='|', s=s)
- ax[2].scatter(fish2, np.zeros(len(fish2))+0.25, color=fish2_color, marker='|', s=s)
-
-
- freq_temp = bh.freq[bh.ident==fish1_id]
- time_temp = bh.time[bh.idx[bh.ident==fish1_id]]
- ax[3].plot((time_temp/ 60) /60, freq_temp, color=fish1_color)
-
- freq_temp = bh.freq[bh.ident==fish2_id]
- time_temp = bh.time[bh.idx[bh.ident==fish2_id]]
- ax[3].plot((time_temp/ 60) /60, freq_temp, color=fish2_color)
-
- #ax[3].imshow(decibel(bh.spec), extent=[bh.time[0]/60/60, bh.time[-1]/60/60, 0, 2000], aspect='auto', origin='lower')
-
- # Hide grid lines
- ax[0].grid(False)
- ax[0].set_frame_on(False)
- ax[0].set_xticks([])
- ax[0].set_yticks([])
- ps.hide_ax(ax[0])
-
-
- ax[1].grid(False)
- ax[1].set_frame_on(False)
- ax[1].set_xticks([])
- ax[1].set_yticks([])
- ps.hide_ax(ax[1])
-
- ax[2].grid(False)
- ax[2].set_frame_on(False)
- ax[2].set_yticks([])
- ax[2].set_xticks([])
- ps.hide_ax(ax[2])
-
-
-
- ax[3].axvspan(0, 3, 0, 5, facecolor='grey', alpha=0.5)
- ax[3].set_xticks(np.arange(0, 6.1, 0.5))
-
- labelpad = 40
- ax[0].set_ylabel('Physical contact', rotation=0, labelpad=labelpad)
- ax[1].set_ylabel('Chasing events', rotation=0, labelpad=labelpad)
- ax[2].set_ylabel('Chirps', rotation=0, labelpad=labelpad)
- ax[3].set_ylabel('EODf')
-
- ax[3].set_xlabel('Time [h]')
-
- plt.show()
- embed()
+
+ foldernames = [datapath + x + '/' for x in os.listdir(datapath) if os.path.isdir(datapath+x)]
+ for foldername in foldernames:
+ if foldername == '../data/mount_data/2020-05-12-10_00/':
+ continue
+ # behabvior is pandas dataframe with all the data
+ bh = Behavior(foldername)
+
+ category = bh.behavior
+ timestamps = bh.start_s
+ # Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
+ # Get rid of tracking faults (two onsets or two offsets after another)
+ category, timestamps = correct_chasing_events(category, timestamps)
+
+ # split categories
+ chasing_onset = (timestamps[category == 0]/ 60) /60
+ chasing_offset = (timestamps[category == 1]/ 60) /60
+ physical_contact = (timestamps[category == 2] / 60) /60
+
+ all_fish_ids = np.unique(bh.chirps_ids)
+ fish1_id = all_fish_ids[0]
+ fish2_id = all_fish_ids[1]
+ # Associate chirps to inidividual fish
+ fish1 = (bh.chirps[bh.chirps_ids == fish1_id] / 60) /60
+ fish2 = (bh.chirps[bh.chirps_ids == fish2_id] / 60) /60
+ fish1_color = ps.red
+ fish2_color = ps.orange
+
+ fig, ax = plt.subplots(4, 1, figsize=(10, 5), height_ratios=[0.5, 0.5, 0.5, 6], sharex=True)
+ # marker size
+ s = 200
+ ax[0].scatter(physical_contact, np.ones(len(physical_contact)), color='firebrick', marker='|', s=s)
+ ax[1].scatter(chasing_onset, np.ones(len(chasing_onset)), color='green', marker='|', s=s )
+ ax[2].scatter(fish1, np.ones(len(fish1))-0.25, color=fish1_color, marker='|', s=s)
+ ax[2].scatter(fish2, np.zeros(len(fish2))+0.25, color=fish2_color, marker='|', s=s)
+
+
+ freq_temp = bh.freq[bh.ident==fish1_id]
+ time_temp = bh.time[bh.idx[bh.ident==fish1_id]]
+ ax[3].plot((time_temp/ 60) /60, freq_temp, color=fish1_color)
+
+ freq_temp = bh.freq[bh.ident==fish2_id]
+ time_temp = bh.time[bh.idx[bh.ident==fish2_id]]
+ ax[3].plot((time_temp/ 60) /60, freq_temp, color=fish2_color)
+
+ #ax[3].imshow(decibel(bh.spec), extent=[bh.time[0]/60/60, bh.time[-1]/60/60, 0, 2000], aspect='auto', origin='lower')
+
+ # Hide grid lines
+ ax[0].grid(False)
+ ax[0].set_frame_on(False)
+ ax[0].set_xticks([])
+ ax[0].set_yticks([])
+ ps.hide_ax(ax[0])
+
+
+ ax[1].grid(False)
+ ax[1].set_frame_on(False)
+ ax[1].set_xticks([])
+ ax[1].set_yticks([])
+ ps.hide_ax(ax[1])
+
+ ax[2].grid(False)
+ ax[2].set_frame_on(False)
+ ax[2].set_yticks([])
+ ax[2].set_xticks([])
+ ps.hide_ax(ax[2])
+
+
+
+ ax[3].axvspan(3, 6, 0, 5, facecolor='grey', alpha=0.5)
+ ax[3].set_xticks(np.arange(0, 6.1, 0.5))
+
+ labelpad = 40
+ ax[0].set_ylabel('Physical contact', rotation=0, labelpad=labelpad)
+ ax[1].set_ylabel('Chasing events', rotation=0, labelpad=labelpad)
+ ax[2].set_ylabel('Chirps', rotation=0, labelpad=labelpad)
+ ax[3].set_ylabel('EODf')
+
+ ax[3].set_xlabel('Time [h]')
+ ax[0].set_title(foldername.split('/')[-2])
+ # 2020-03-31-9_59
+ plt.show()
+ embed()
# plot chirps
if __name__ == '__main__':
# Path to the data
- datapath = '../data/mount_data/2020-05-13-10_00/'
+ datapath = '../data/mount_data/'
main(datapath)
diff --git a/code/plot_introduction_specs.py b/code/plot_introduction_specs.py
index 3f8395e..20fb562 100644
--- a/code/plot_introduction_specs.py
+++ b/code/plot_introduction_specs.py
@@ -41,9 +41,9 @@ def main():
freqtime2, freq2 = instantaneous_frequency(
filtered2, data.raw_rate, smoothing_window=3)
- ax1.plot(freqtime1*timescaler, freq1, color=ps.gblue1,
+ ax1.plot(freqtime1*timescaler, freq1, color=ps.red,
lw=2, label=f"fish 1, {np.median(freq1):.0f} Hz")
- ax1.plot(freqtime2*timescaler, freq2, color=ps.gblue3,
+ ax1.plot(freqtime2*timescaler, freq2, color=ps.orange,
lw=2, label=f"fish 2, {np.median(freq2):.0f} Hz")
ax1.legend(bbox_to_anchor=(0, 1.02, 1, 0.2), loc="lower center",
mode="normal", borderaxespad=0, ncol=2)
diff --git a/poster/figs/algorithm.pdf b/poster/figs/algorithm.pdf
index 2e2c453..359d7e6 100644
Binary files a/poster/figs/algorithm.pdf and b/poster/figs/algorithm.pdf differ
diff --git a/poster/figs/chirps_winner_loser.pdf b/poster/figs/chirps_winner_loser.pdf
new file mode 100644
index 0000000..f89f5ac
Binary files /dev/null and b/poster/figs/chirps_winner_loser.pdf differ
diff --git a/poster/figs/introplot.pdf b/poster/figs/introplot.pdf
index cbead3e..7fef6fa 100644
Binary files a/poster/figs/introplot.pdf and b/poster/figs/introplot.pdf differ
diff --git a/poster/figs/logo_all.pdf b/poster/figs/logo_all.pdf
new file mode 100644
index 0000000..f54eedc
--- /dev/null
+++ b/poster/figs/logo_all.pdf
@@ -0,0 +1,529 @@
+%PDF-1.5
%
+1 0 obj
<>/OCGs[15 0 R 18 0 R 21 0 R 25 0 R 35 0 R 36 0 R 37 0 R 38 0 R]>>/Pages 2 0 R/Type/Catalog>>
endobj
34 0 obj
<>stream
+
+
+
+
+ application/pdf
+
+
+ EKUT_WortBildMarke_W_RGB
+
+
+
+
+ Adobe Illustrator CS3
+ 2010-07-08T15:07+02:00
+ 2010-09-21T13:10:33+02:00
+ 2010-09-21T13:10:33+02:00
+
+
+
+ 256
+ 68
+ JPEG
+ /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgARAEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXmPm/RNIu
/wAwrs3HliLzAZdEjaWLjAJARcSJzDSlW5cRxBQ8qDbtkCN3a6fLIYRU+D1+fcl/5kaM15511RbD
SF1DUp/LLGJk9NJo5RclEuI2biTJEPs0YE0pXBIbtmiycOKNyqPifo5fFV8w/oPXvMXkXUYtLj8y
297puoMEnjg5zqiWxRnFxxTkrOxoTsScJ3pGHjxwyRMuAiUe/bn3KHmODydp3m7TrjWtDX9EweVp
PVsjbi5a3SC4txGHoGp6K1XnX4fHAatOE5ZYyIS9Xic7q9j9679B32nzflrDeW8epanb3F+YRPIr
sIvq801vGbikgJhThuKjktR2ONcl8USGYg8MSI/eAdvNOtSinuPzV8p3OoaZBa3AtNU4TK6zO3BY
ONW4IRw5mn+scPVogQNPMRJIuP6UDqvmae3/ADIttaEk/wCiLe6/w5cJ6Mv1bjOAzXHr8fRDLecI
SOdfhO2JO7ZjwA4DDbiI4+e+3Suf07qfnSLybbeedZn1/SlureXRrAmdIEdknku7mFH9Ugek7Hgo
kJHQVO2Jq06Y5TiiISo8cuvSgfj7l0nlcR65+W9h5ihg1LUYbS+t72WZFn5mK1UqrM4+PgTsT337
41yQM/ozGFxjYrp1TeTTI/LH5gaBBof+i6Try3cF/pMZItke2gM8dxDF9mI/D6bcaA1HfDVFpE/F
wyM95Qqj13NUe/vS782L+HRfMvlrXUs45bq3jvUa9eP1Pq0TCJTcOq/G6QCRn4r79Kk4Jc23QQM8
c4Xsa27+e3xTDW9M0XRPKbWWmF5LrzTOtvc6lBG091c/WqyXVz+5VmdhbiV14rxG1KDCdg14pynk
uXLGOXICuQ386SS51L9MfkVrdtqKerf6NaXNheCeMq/r2S0jlKSAMrMnCTcAiuD+FujDg1cTHlIg
j3H8UmHkvT9HufNs1zoVmNP0+20/6l5gsGjECTXcwilhb6ttuIuRaQrRgwAr8VEc2vUzkMdTNkyu
J57b3v7+iN8g6LG35XyadpZXTprxdRjjuIVClJZJ5o0l+HulFp7AYYjZhq8v+EcUvVXD9wb/AC1k
0yOe802bRItC80adBBBqcEChYp4QX9G4hZQBIjnlufiU7HGK60SIEhLjxyJry7wWd5J17sVdirsV
dirsVdirsVYB56/NW18v6n+h7T6uLxVBur2+do7S3Z0aSONuAZnkdVqF+EbirCoyJlTsdLoDkjxG
67hzP7GO6F/zkFpq2sU/mFrUxT2n1pJ9NMr+lMFY/U54pRVJmMT8Dy4t7AgmIm5OXsiV1C+db/eP
JCN+ePmyfU5rS20eCK5SJrhNHaK8urwIpUgTNbIyxmRWDL8JA6NTu8ZZ/wAl4xGzI137AfC2feQv
zM8u+cbOM2r/AFXVDH6txpUxpMi148lqF9RPBl+mh2yUZW67V6KeE77x72XZJw3Yq7FXYqx7UPJ/
1vX5dch1i/sbuW0FgUtjbGMRK5kBVZoJTz5MTyrgpyYaiocBjEi73v8AQVl15Jjl14a3DrGoWl4t
iNNAia2dfQD+pU+tBKxctuWLVxpMdTUOAxiRd9f0FSb8vNNjn0SXTr670xfL9tJaafFbfV2QRzBR
IX9eGYszemu9e3zwcKfzkiJcQEuM2bv9BCq3kWxl1SHUbu+u7x00+TSp4ZzA8c9vMwaX1qRByzso
J4sPYUw0j80RHhAA9XF12PzULX8vLW3bQyNX1GQeXWdtNEjWzUWSMw+m7ehyZRExUb1964OFlLWE
8Xpj6+fP396P1byrFqOvWGtfpC6tLrTYZ4LZIPq5jpchRIzCWGUljwXvTbp1qSGrHn4YGFAiVd/T
4qB8iaU/k2bypPcXNxZTrJ6l1I0f1kvLKZzLzWNU9QStyDcOu+NbUy/NS8XxABf2dylN+X9hdXl5
c6hqF7fi/wBOGlXcE5t/Te3UuVP7uGNuYaVm5A9Tg4WQ1ZAAiAKlxdefzWyeQuU+j3I13UTd6Gkq
WMz/AFRyVmUIwlrb/H8C8a7H3rvjwqNXtIcManz5/rTLTvLMFtqZ1a7up9S1T0zBHc3JQCKJiGZI
Y4ljjQMVHI05GgqTTDTXPOTHhAEY+X6WtV8r2upa5p2rT3M6tp0c8SWa+kbeVLkBZRKrxuzVCgbM
P14kLjzmMDEAeqt+uyA0b8vdM0jUrS6tb69a108XA07S5Xie2t1uac1jrH61Bx+Csh4ioGxpgEWz
JrJTiQQLNWepr419i3Vfy803UZNeb6/eWsXmOKOLU7eAwemfTT0uaepDIVdk+FjXfExXHrJR4dge
Dlz/AFou18nW1prC6tb393HeG1js7wgwcLpIa+k8yelx9RORoycdtumGmEtSTHhIFXY57e7dAaf+
XSWGlJpVv5g1UWMZndIuVmCHuC7s5dbZXPF5WdQTSvUECmDhbJ6zilxGMb27+nxTzTdDSzvJ7+e5
kvdQuI44XuZljUiKIsVjQRJGAvJ2Y9yT8qGmieWwABQCZYWp2KuxV2KuxV2KuxVAeYNVGkaDqWrM
nMafaz3RT+YQxtJT6eOAlsw4+OYj3kB8tXc2lx6kb6/eHzBrk4a7nlt2kuoLiUejct9bhVXX6vEp
eALG32kJNKfDS9ZESMaHojy7q5jY955796j5u0mW4tFXWLcW99HaGexuxEYXjtYWZPTntVjgkJkk
ZWWSjcUPQgFsSE6fIAfSbF7+/wAjZ+Xe28es6vbC208JbaZHZzNp9y0RE8quI0u5o1grct6v1Wbk
8nMMocUHTFQYwNy3lYvu60N9trHKuiY6VeHQNXtZtSjMEllcacbbUF/cJYelcBNQtkgMknKJ1uGa
T06cmZWC8GDYRs1ZI+JEiPUS2/nbek3XPba/dzZ/oGi2i/8AOQ+rWCyTjT7CwXULWzM0hhWdvQWv
Akig9ZmA7H5ZID1OvzZT+SidrMqvy3/U9nmhjmheGVQ8UilHU9CrChGWOkBo28r/ACe0q2g80+dm
DzS/UNTezshNLJKIoAWbggcn2367ZCPMu27RyE48fnGz72MflX+Z2q+X5bPSPNzkeX9XeZtA1iQ8
lRo52ieCRz+wrgjf7G37BBEYyrm5Wv0Mclyx/XGuIfDn+Ofve83NtBcwPBOgkhkHF0PQg9stefjI
g2HnX5TaLZpJ5yWUNN6euX2nxepJJIEs1WJkhXmxoBzyERzdl2hlP7v+oD8d90h/JfSLWTzX58hu
HmuotN1CTT7KO4lklEdv6syFQGY7lUAJ64IDcuR2lkIx4iNuKNn37KAs08mfn1YwSvI2g+YIGXTU
llkeOC5agKpzY7+olB4CSmPKTLi8fRk/xwO/mPx9z2e+ntbexuLi7YJaQxPJcO3QRqpLk+3EZY6S
AJIA5vOfyh8q29z5en8xanC7TeYJZ7m2tZZHZLexmc+jCiliqgp8VR2IyEQ7LtHORMQj/BQ956li
/wCVvk7Rda8xedrHVWvLu30bVHtNOU315GY4llmXjWKVOWyDrgiObl67UyhDGY0DKNn0jy8nqWhe
R9M0vQrzRHeW7sLueWYCaSRpFSSnFPVLF/g47NWuTAdTl1UpzE+RAeb/AJK+UtH8xeUbu61s3V9d
R39xapO95dKyxRqgVRwkUClTkICw7PtPUSx5AIUBwg8h+pNfI135h8v/AJpap5Gu9QuNV0VrIalp
k145lnhXmq8DI1WK1Zl38ARSpwjY01aqMMmnjlAEZXRrkiNbvLPXPzeXypr07ppMGmrc2GmiR4o7
u5d/iaTgUMnBAeKGo2JxO5pjiicem8SA9XFRPcGU6D5F0vQNeuNQ0gNbWd3brFNYh5Gj9VHqJVV2
YKSvwmmSApxMuqlkgIy3IPNgOm6Ha/8AQw19aLJMLC10xdTisvVkMIuC0UdQnLjT94Wp0rkK9TsJ
5T+SB6mVX5bsz/5Vf5en1O+1XUZby5v76d5mkjvLu1VEPwxxolvLGKLGFWp6/hkuFwvz0xERjQAH
cD94YT+XXlyx1jzT52sdRudQntdI1BbfTo/0lfp6cRMnw1SdWb7I3Yk5GI3LnazMYY8ZiI3KO/pj
5eT0Lyx5Is/LqapBZXVy1tqLq6etM8skNI+B4SSFm6/EMmBTrs+qOThJAseTyWXWdX03z/ZaX+lb
5/K0izeUf0vJNyuPrnFZXn5kcRIk8yxq3gh8Mrvd24xxlhMuGPif3ldK7vkL+L1bW/INjrVro9jq
N5dzWOkgll9Z0muJBGI0eWeMo+w5E0pUn75mLqcWrMDIxAuX2fBgH5ieWtP0bzR5I0/TLjULez1b
UDbX8Q1K/YSRAxgLV52K/aP2aZGQ3DsNHmM8eSUhEmMbHpj5+TNm/LDRIdV07VNPub63ubCdJSst
7dXMciCvJHW4ll612p3yXC4P56ZiYyAII7gPuDMck4TsVdiqF1XT4tS0u806Y0ivYJLeQjeiyoUP
4HEs8c+GQkOhfHXlSa2W0uz6U1/e3cElrbPHBNza5nrBFAJkZWPKFWcLUdCDyFVzHD2mcGx0AN8x
yG917/wE+8822o6jIZ/M8DWurXUbSNHNbxxS28UckcdoYJYKfWllQ+lwVfgJZm6DCfNx9LKMdsZu
I8+fO7v6a5+aYaLZSahp0t2I4r3T7HjFqNlqCtaWKRCP0lpIjpI8iyep9Xj+MInxfCz4Q15ZcMq5
E8iNz+OXEdrPeAh7qx0x/NbaVa6farqxuJ7WHS7eO54SQ6lBJC8vpzrE0bWv97vHUqwo3FQcerKM
peHxEnhoG9v4TfTv5c3p+jfu/wDnJXXefw+toiel70a2/wCaDkx9Tqsm+hj/AF/1vWsm6d5x+U8b
f4i8/wAv7Da7KoPuoqf+JDIR6uz7QPoxf1EB5F8naN5u/Jey0fVErG8t80M6gepDKL6fjJGT0I/E
bYxFhs1Wplh1RlH+j8fSEJ+X3nLW/KOvr+XfneQlwQvl7WX/ALu4i6JGzn7lqdj8B7YImtiz1emh
mh4+L/OHd+P2so/K3+/86f8AgTX3/JqDJR6uJruWP/hY/Sx78lP+Uw/Mr/ttyf8AJ+4wQ5lye0/7
rD/U/QEf+fvlyfUfJY1mxquqeXJl1C2lX7QjUj1qHwAAk/2GMxs19k5hHLwn6Z7fq/V8VnmTzKPO
Hkjy5pmnOUuPOzRwz+mfihtoh6moEf6gQxn/AFsSbHvThweDlnKXLF9/8P63pVvbw21vFbwII4IU
WOKNdgqKKKo9gBk3Vkkmy8Q/LXR9e1Dzl+YZ0rXptF9PW5hKsNvbXAkrPPQn6xHIRx9sriNy73W5
IRxYuKPF6O8joO5635S0/W7DRUttbvm1LUVmuDJeMFUyRmdzCeCAKn7rj8I6HJh0+onCUrgOGO23
w3+14v8Alf5q81eWvy51zVbPRINU0uyv7qaR/rbQTrQIJP3XoyhlQfETzG1criSA7zXYMeXPGJkY
yMR0sfe9J/LjTbe/jbz3cXS3+reYbeI+pGvpxW1uoqLWJeTn4HHxkmpYdsnHvdZrZmP7kCowPzPe
t/M/8r7PznawXNvOdO8w6f8AFpupJUEEHkEcr8XHluCN1O47gso2uh1xwEgi4HmEt/KXz15hv73U
PJ3m6L0vNGiKGebb/SIKgCQ02qOa/ENmDA+OCJ6Ft7Q0sIgZcf8Ady+wqGmf+tHav/4Dy/8AJ+DH
+JlP/Eo/1/0F6lk3UvLfyj/5Tf8AMj/trL+ubIR5l23aH91h/q/qZ35v8wweXfLGp63NQrY27yop
2DSUpGn+zchfpyRNB1+nwnJkEB1LxrzB5c81Tfk8miy+WL5NSsT+lZNUa4sT/pXNp55eK3BmqVkc
ABeXTbKyDTu8ObGNTxCceE+mqly5DpT1r8vPNKeaPJul60CPWuIQt0o/ZnjPCUU7fGpI9qZZE2HT
6zB4WWUO77mH/m5/ym/5b/8AbWb9cORlzDm9n/3Wb+r+t6lk3UuxV2KuxV2KvCfMf5N6r5YsrjVP
L0Ntr8UbSFdCurUzBI2WUI0atN8boZa0pSpLKtdjWYU7/D2lHKRGdw/pA+7yeN61pPm3TfL1rq90
7jStSmeCzklP77/RQYiAjfHGlFoF/wAkeAysgu7xZMcpmI+qPP4orSLHX5fLWmailtbXGnW2oSvD
Hey/uZJ7eMTTB4SURg8fENyJYhKCgrVHJhklAZDGzxGPTuO3P8c3on5LaJBrPmHS9Qhi9dtDeaaT
WObqotmWWG2shEWYK3Nmk+LcRhRUdMnAOt7TymEJRP8AF089iT+j329G8+eUPMcPnHS/PnlWFLzU
rGI2uo6U7iL61bGv2Hb4Q45nr7eFDIjew63SaiBxSw5NoncHuKZP59165tzFpflDVf0owCol8kVt
bIx6tJOZGBVf8gEnDxeTUNJAG5ZI8PlufkivJvl248o+WJluS+p6tdTy6hqj26jlNd3LAyemHKCg
2A5EbCu2IFBhqcwzZNvTEChfQBKvyZsdc0ryoukazpc+n3cM1xNWQxtGyzSmReLRu2/x03HbGHJu
7SlCeTijIEUE78++RNH856FJpmoKElWr2V6orJBLTZ16VH8y9x9BwyFtGk1UsE+KPxHekX5QeX/N
Hlzy/rVv5kVp9ROpzTrMhEhuYhbQIkiHavP06fFQ1674Ighv7RzY8s4mH08Py3KUflLpvmnSvNfm
y41fQrmytfMN+19azs8LqgMkr8JODkg0kG4BwRu27tCeOeOAjIEwjXXyeqzwQ3EEkE6CSGZWjljb
cMrCjA/MZN1IJBsPK/ye/LXVfLWsavPqrM9tp8s1h5dWTelrLIJ5Jl7fvPgHzDDIRjTtu0dbHLGI
jzO8vfyr4PVZZPTieTiz8FLcEFWagrRRtucm6kC3jn5eDzl5a8webdQ1HylqMltr1899a/VntHdA
ZJX4Orzx9pBuCfllcbFu61nhZYQjGcbhGt78vJ6DoGv6/fWOpanf6Lc2CRycNP0uT0zdSIkalnNG
4AvIxABboMmC67LihEiIkD3nowz8kNA8waRoOr6D5k0Sa1S/vJroPI0UkLxTxJG0TcHY1/d+G9cj
AOb2plhOcZwldAD5K/5baT5r8j3Go+WbvTLi+8ui5efRNTt2ifhFId45UZ1dfHZTvXtTGII2Y63J
jzgZAQJ16gnb+afMmj+YtXtdS0LUL/TJpo5dIv8AT0W4T0jBGrROnMNGRIjGtKEk/Mm2gYIThExl
ESrcHbqpeU9B1W8876n541WybS2urRNN03T5GRpxbo4keWf0yyBnZRRakgdcQN7TqMsY4hiieKjZ
PS/JILaz83Rfnfd+ZW0G7GhT2X6NFyrQliF4N6hj9Tlx9SP5039sG925MpYzpRj4hx3fV6vK5jid
wrOVUtwXdmoK0FabnJuoAeVflXY+bdM84+bLnWdDubSy8wXpu7O5ZoWEaiSUhJQjkj4JF3A7b5CN
27bXyxyxQEZAmAo8/JMvzMi8wa1f6Lo1pot3c6HDqEF3rd0nohZIYGDLEitIrOpbdtu21cZNWiMI
CUjICfCRHn1eikAggioOxBybrXlH5X6N5o8ma/r2gy6RcyeVrm9efR72NomWIMeNHUyc+JQLvTt0
3yEQQ7fXZMeeEZ8Q8QDcN/m3pvmzUfNnlK60XRbi+tdAvBe3k8bxIHHqRN6cfN1NeMbVqO+Mrtez
54445iUgDMUPtepW8xmgSUxvEXAJikADr7MASK/Tk3UkUVTFDsVdirsVdirwz/nKz/lH9C/5i5f+
TeV5Hfdg/XL3Ir8gNC0nXfylu9M1W2S6s7i+uFeORQaExRjkpP2XWvwsNx2xgLDHtbLLHqRKJo8I
etaHoOjaDpsem6PaR2VlF9iGMbVPVmJqzMe7Ma5MCnT5csskuKRso/C1uxVh02ufmRNqeoLpmhWU
mlwTmGznvLqS2lkEYAd+Ail+EyV4nbbx65Gy5oxYBEcUjxVvQv8ASkHl78yfP+vaxrek2Pl/Txda
BMkF8ZL+VULuXA9Mi3PIfuj1AwCRLkZtFhxxjIylU+Xp/an1v5l89LpeuTaholra3+lKJbWAXDvD
cRemzsVmCbGqkCq/Pxw2XHOHFxREZEiXly+DX5Z+eNY84eW5NeudMjs4Gd0s4IpvVkl9LZieaxqt
W+EVOMTadbpY4Z8AN97fmPzpr+l+Qv8AFKaKIriCMTX2k3k3CWNeXE0eMOpI+1Q028DtiTtaMOmh
PN4fFt0Ibu/NnmSD8u/8Vrp1q90ln+kZtP8AXcKLf0xKQJeG7rHU/ZpXb3xva1jp4HN4dmrq66pG
fzR802/kuz86XOg202hzqst1Ha3btdQxO3AScJIUR6HqA/4VIHEatv8AyOM5TiEjx+Y2P2syvvME
svlJvMGhpHehrT69axzMYlkj9P1AOQDcWK+I69clezhRw1k4J7b0lf5YedNU85eXRrt3YxWFvNI8
drFHK0rMIzxZmqqAfECAMETbdrtNHDPgBsobyp+aOmaz5t1rypdBbTVtMuporVeXw3MMbEckr+2o
HxL9I70RLemWo0MoY45BvGQ+TN358G4U50PHl0r2rTJOAGH+WfN+v6p5w13y/eWNrbpoH1f6xcRT
SSGT63EZYuCtGlNh8VTkQd3Nz6eEMUZgk8d9O5U1TzzO+uzeXvLOn/pjV7UKdQleT0LK05/ZE83G
Ri56hEQn5Y33IhpRwceQ8MTy6k+4LbmT82ok9WCHQrggEm253cbHwCyEMCfmox3WI0x58Y+SI8me
Z9c8waPfTX2lLpGrWN3JZSWck3rLziRG5F1Vdjz2pXbepriDbHU4IY5ACXFEi7Yz5U/Mbz55lvta
srLQ9NguNBuTaXq3F9OAZQzqfTMdtICKxnc0wCRLlZ9HhxCJMpVMWPSP1sz8p6nruo6fPJrdimnX
sNzLB6ETmRCkZAV1dgvIN1BoMkHC1EIRI4DYpIfyy/NLTPOtvcwFVtNZsXZbqy5V5IG4iWKu5Q9/
5Tt4EiMrcjW6GWAg84nqzS4+seg/1bgZ6fuxJUJX3pvknBFXuxbyF5u1jzJLrX1yyt7OLR9QuNKb
0ZXlaSa2K83HJEASjbd8jE25er08cXDRJ4oiXzZbknDdirsVdirsVdirEvOv5p+TPJxEWr3ZN6yh
0sLdfVnKnoxWoVQe3JhXImQDmabQZc30jbv6PnX86vzSuPOcum20emy6bplspubb6x/eziYALKQP
hC0X4aE998qnK3pOzNCMFm+KR29yffkF+bmh+WbK48ua6Wt7a4uDc2l8qvIA8iojROiBmAPAFSB4
1wwlTR2t2fPKROG5qqfSFjqVhfxNJZzpOqNwkCn4kelSki/aRxXdWAI75c8xOBjzCJxYuxV2KvJf
yf8A/Jj/AJnf9tCD/k5dZCPMu47R/uMP9U/716V5i/5R/U/+YSf/AJNtki6vD9Y94YH/AM4+LI35
TWCxsEkMl2EcjkAfXehptWmRhydh2v8A4wfh9yM/MS11a1/JrWLfV7xdQ1GKxK3N6kYhEjcx8XAE
gbf5jDLkw0conVRMRUb5K+o/+SOuv/AZf/unnH+FjD/Gx/wz/fPPl8x6rD+Q9hpo0G/Fnd2YtrnW
FWCaCK2dyJZxHFM83wpWgdE375C/S7HwYnVmXFGwbre77uVfe9Q06bQJPy1A8v3C3OjxaW8NnMvU
pFAU+KoBD/D8QIrXrk+jqpiYz+sVLi3+aQf847/+Sr03/jLdf8n3wQ5OR2x/jB+H3MWf8uz5th80
6hpkgsPNWjeZb1tI1BSUJ4rDKI5CO3JvhP7J+mo4bcsazwTCMt8csYsfNmP5WfmYfMkU2ia5H9R8
4aXWPULJxwMnA8TLGv8AxIDofYjJRlbha/ReF64b45cipaJMbD8xfzLvkHN47bSrjg3QmOzmoNu3
wYBzKco4sGEecvvCj/zj0gk/L0anKxkvtUvbq6vp23Z5TJwqT8kGMOTLtc/vuHpEAB6Lf39rYWr3
V0xSCPd3Cs9ATStEDHJuthAyNBKvLfmvylrsl0dAvIbxgwku3gU05lQgLtxA5cVA61oMAILdm0+T
HXGK7nlf5Y6/JpXnL8xQmlX+perrUxrYxJIF4zz7NzeOhNdshE7l22uw8eLF6ox9HX3B615W8wL5
g0aPVFs57BZJZ4vqt0Ak6G3meFhIgJ4tyjO1dsmDbp8+Hw5cNg8uXLcW8R0X8vNTvvIWiedPKTm3
846ZLeEBTQXcUd7OPTcHYsF+EV6r8J2pSsR2sO9y6yMc0sWTfHKvh6R+Ptep/ln+ZGneddH9ZQLX
WLX4NT00k8opAacgG34NTbw6HcZOMrdTrdFLBKucTyKG/KtVWbzqFAA/xPfGg23MUBJ+/GPVlr+W
P/hcf0s6yTgOxV2KuxV2KuxV8r/nHZ6xqXmbUFaWOC1S4V7mCGotLUsPhe+uD8D3TJv6aciF+FSa
ccplzes7OlGOMd9fE/1R/N83mWo3jS2dtbLyktrOSeO0uXFCYmYOI/8AYsxeni+QdrCNEnqav8fj
kmHly8a1t5LiJ9TsUhcfX9R02U0WNxSIPF+735cxyMoBrSleqGvNGzR4T3A/j9D3b8gL7UpEVLSZ
bjR1jMP1WS9aSeAc2kEjQJCLdWdidufPrXlTLYPP9rRj12l7tj8bv9D2/LHROxV2KvIvydmhb8yP
zNCyKxbUISoBBqFkuQxHyJocrhzLue0QfAw/1f1PWbqBLi2lt3+xMjRtUV2YUOx+eWOniaNvKfyB
1AaZpmo+RtUYW+vaLeTVtHNGeB6MJI6/aXkSduxB75CHc7ftaHFIZY7wkPtTz85NQWTypL5Zs6T6
95haOz0+yU1ch5AZJWHURxorFm6DDPlTj9mwrJ4h+iG5Ka+eIIrD8r9etTJ+7t9GuoFdqCtLVo1+
ljieTVpTxaiJ75j70B+Uk9ifyn0SSeSP6olmVuWkK+mFVmEgcnYAb1rjHk2doA/mJVztiX5QafJY
fl15tuVLR6Hc3F/No/qVUG2WIqJQW/ZYL+GRjycztGfFngP4wI3706/5x1kjb8rNPVWDMk9yrgGp
U+szUPgaEHDDk0dsD/CD7h9yM/Km5R7/AM8QqQfT8yXjFga/aSNaU9jHhj1Ya+O2M/7WP0qP5o/l
pd61NB5n8sTfo/zjpnx206UQXCqP7qQnatNlLbU+FtjsJR6hOh1ogDjyb45fYk35N+YLrzT5n87X
uq2JsrySLTLXULFwQFlhjuIpRRviAJXoenTBA2S39pYRix4xE2PUQfklv5e6yfyu16/8j+aXNto1
1cNc6BrEu0Dq9FKO/wBlagKT2Vq12IOMTWxbdZj/ADUBlx7yAqQ6vbFubdoPrCyo1uV5iYMCnGle
XLpSnfLHRcJuury7/nG9YR5EvTCFCNqtyVK9COMYH4ZDHydt2zfjC/5o/Sh/yRngk84/mQEkVy+s
vIoVgaoZ7ijCnb3wQ5ll2mD4WH+p+gPULDUdNuZ7y2snV2spAl0I6cVlkUSkbftEOGPzyx1M4SAB
PVhn5Ez+r+XFoOQZYrq9VaU2BupH7f61cjDk53aorOfcPuSr8yPIGs2GsL5+8iAQ+YLYE6lYIPhv
oti/wDZnIHxL+11HxgVEo9Q26LVxlHwc30Hke78fjZFfkRrK63o3mPV1ha3F/r1zcGBjUoZLe3JS
vfidq4wLHtXHwShHnUB95el5N1bsVdirsVdirsVeT/mT+Vl3r2u2lxDapd6JEjST2SymOT12lluJ
2WpX4rhvSj5VqFr02yEo27fRa8Y4EE1Lv8tgPluXneo/kz5tv9Tg0UaZIqNOqTa84hS0ihSpkW1t
o2/dR8mqC1XkND8NWrDgLsodpY4xMr6fTvfxPX7h5rbD8kPPM9zS0t5ND1i2T0Ly4aRDp92g25pI
haQeoAOaGJgTuafZDwFM+08QG54onl/OH477ew/lv+Xl3oAF/rM8lxqioYreL1xLbW6PQv6MSQ2y
IzkfE3Ekjv1yyMadLrdYMm0RUfduffuWe5J17sVWTen6Mnq/3fE8+v2ab9MUjmwry5/yqH9Mn/Dv
6M/TG/P6lx9f7Qrz4fFTlSvLvkRTnZvzPD6+Lh82cZJwGE+fP+VU/Wrf/F31L9I7fVuXL65Su3D0
P3/Gv0ZGVdXO0n5ij4d19n27L/I//KsPrlz/AIW+q/pOh+t15/XeNf2/rH7/AI18dsRXRGq8eh4l
8P2fZsnnmr/DH6Hk/wATfV/0RyX1vrdPRrX4edfh69K98Jrq0YPE4v3d8XkwWz/6F3+tt9X/AELz
qnqfY9Cv7HLl+569K98j6XYS/O1vx/p/WzzXP8N/oKT9MfV/0HwHq+rT6v6dNuVPh4U+jJF1+Lj4
/TfH9qT+VP8AlWv1W8/wt9R+q8H+u/UKenSg5cvT+Hlxp74BXRuz+PY8S76Wt8q/8qx/Sc3+GPqH
6RqfrP1KnPl8VfV4d/tfaxFdFz+Pw/vOLh82X5Jw0ssf8Pfp3U/qP1f9M8Lf9L+lT1eNH+r+tTvx
5ca709qYG2fHwC74d6/TS3zN/hf9FP8A4m+p/ouvx/pD0/R5dv734eXh3xNdU4PE4v3d8Xk8w/6x
rq1Pqno8vi4/XPq9fo/c5D0u0/w7z+y/1vTND/wp+gf9wP1T9B8Xp9Q4ehSnx09Havj3yYp1eXxO
P13xef7Xn97/ANC27ev+hK1NfT48q9+Xp7/fkfS7GP57pxsq8sf8q2/wrd/4d+qf4c9Vvrv1evpe
rROXqd68eFa9qdsIqnEz+P4g474+lq3kv/lXf77/AAf9S4b+v+j+PCu32uHw16e+IrojU+N/lb+L
KMk4iV6H/h3nqX6F9Cv11/0p9XpT67wT1OfHb1OPDl+O+ANuXj2475bX3JphanYq7FXYq7FXYq7F
XYq7FXYq/wD/2Q==
+
+
+
+
+
+ uuid:4843E9BF348CDF11938FC449556742DA
+ uuid:b47a17de-e854-6642-a62a-d66d7f6f8213
+ proof:pdf
+
+ uuid:4743E9BF348CDF11938FC449556742DA
+ uuid:4643E9BF348CDF11938FC449556742DA
+
+
+
+ 1
+ False
+ False
+
+ 194.999984
+ 50.000049
+ Millimeters
+
+
+
+ Cyan
+ Magenta
+ Yellow
+ Black
+
+
+
+
+
+ Standard-Farbfeldgruppe
+ 0
+
+
+
+ R=165 G=30 B=55
+ PROCESS
+ 100.000000
+ RGB
+ 165
+ 29
+ 54
+
+
+
+
+
+
+
+
+ Adobe PDF library 8.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
endstream
endobj
2 0 obj
<>
endobj
40 0 obj
<>/Resources<>/Properties<>>>/Thumb 44 0 R/TrimBox[0.0 0.0 552.756 141.732]/Type/Page>>
endobj
41 0 obj
<>stream
+HdWn-
ܟ8}%RY
"``&@?U(]ZHǯ?˟~^?_?|)tr*wzx+ݭ+9ܮ?~{ۿ__Jɗzϙ[|,ߦ/(ok*wUj~WK^U5 t;ghwN]U}CXS
J_(nƂh g7Es
+R%+U%xƥ)㿓㥸3vҽ%5n=rB{$._#UUN/p]Ҿ.mpn>
+a}EMxikQՏ9lLd9pjL=ƇY(y&_ER$EJ2,)uX*Y\l
d| vSݔMeCb=
iV?tǂܴ9>F(qiI^K=
+O9LyEGa>Ĺ|9p~
+bL0Ge^3U=7&)ꛭqa+{SO5,?\QУq\$X(cxHnC2fvH<^6cЬ5?CdI J͔I$I.fugwRW0Moǽ.}>K'v =mBGrDF]Ώ'
3DXُCoݒo3n =qIGzMgE103Gsj̥HN[^R .zW@n EFh[v:V؋gdy|v8|J݆,YxC242ufkÀ] ,m)os҆!G5hmuy+F]FynBn( ̾78i}xxhs(wN:ua֦, v`ZuGjzD/!i+J,&by^ܭ_qt L%=}t1TxAݦ)`
#AĚeC㹟}