From fecee5ee7073ee34dce967d29923faf076f72f79 Mon Sep 17 00:00:00 2001 From: Nels Schimek Date: Tue, 16 Apr 2024 18:40:31 -0700 Subject: [PATCH] adding key files --- .coverage | 16 + .coveragerc | 7 + .gitignore | 14 + CONTRIBUTING.md | 62 + LICENSE | 21 + README.md | 39 + diff_classifier/__init__.py | 0 diff_classifier/aws.py | 85 ++ diff_classifier/due.py | 74 ++ diff_classifier/features.py | 901 ++++++++++++++ diff_classifier/heatmaps.py | 524 +++++++++ diff_classifier/imagej.py | 426 +++++++ diff_classifier/knotlets.py | 465 ++++++++ diff_classifier/msd.py | 1046 +++++++++++++++++ diff_classifier/pca.py | 787 +++++++++++++ diff_classifier/scripting_instructions.md | 59 + diff_classifier/utils.py | 68 ++ diff_classifier/version.py | 54 + .../diff_classifier-0.1.dev0-py3-none-any.whl | Bin 0 -> 53692 bytes dist/diff_classifier-0.1.dev0-py3.7.egg | Bin 0 -> 106967 bytes dist/diff_classifier-0.1.dev0-py3.9.egg | Bin 0 -> 107123 bytes dist/diff_classifier-0.1.dev0.tar.gz | Bin 0 -> 45284 bytes doc/Makefile | 192 +++ doc/make.bat | 263 +++++ doc/source/api/aws.rst | 9 + doc/source/api/features.rst | 9 + doc/source/api/heatmaps.rst | 9 + doc/source/api/imagej.rst | 9 + doc/source/api/index.rst | 14 + doc/source/api/knotlets.rst | 9 + doc/source/api/msd.rst | 9 + doc/source/api/utils.rst | 9 + doc/source/cloudknot_parallelization.rst | 13 + doc/source/conf.py | 180 +++ doc/source/example_data.rst | 31 + doc/source/features_analysis.rst | 69 ++ doc/source/getting_started.rst | 72 ++ doc/source/index.rst | 129 ++ doc/source/interacting_with_S3.rst | 21 + doc/source/tracking.rst | 93 ++ setup.py | 42 + 41 files changed, 5830 insertions(+) create mode 100644 .coverage create mode 100644 .coveragerc create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100755 LICENSE create mode 100755 README.md create mode 100644 diff_classifier/__init__.py create mode 100644 diff_classifier/aws.py create mode 100644 diff_classifier/due.py create mode 100644 diff_classifier/features.py create mode 100755 diff_classifier/heatmaps.py create mode 100644 diff_classifier/imagej.py create mode 100755 diff_classifier/knotlets.py create mode 100644 diff_classifier/msd.py create mode 100644 diff_classifier/pca.py create mode 100644 diff_classifier/scripting_instructions.md create mode 100644 diff_classifier/utils.py create mode 100644 diff_classifier/version.py create mode 100644 dist/diff_classifier-0.1.dev0-py3-none-any.whl create mode 100644 dist/diff_classifier-0.1.dev0-py3.7.egg create mode 100644 dist/diff_classifier-0.1.dev0-py3.9.egg create mode 100644 dist/diff_classifier-0.1.dev0.tar.gz create mode 100644 doc/Makefile create mode 100644 doc/make.bat create mode 100644 doc/source/api/aws.rst create mode 100644 doc/source/api/features.rst create mode 100644 doc/source/api/heatmaps.rst create mode 100644 doc/source/api/imagej.rst create mode 100644 doc/source/api/index.rst create mode 100644 doc/source/api/knotlets.rst create mode 100644 doc/source/api/msd.rst create mode 100644 doc/source/api/utils.rst create mode 100644 doc/source/cloudknot_parallelization.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/example_data.rst create mode 100644 doc/source/features_analysis.rst create mode 100644 doc/source/getting_started.rst create mode 100644 doc/source/index.rst create mode 100644 doc/source/interacting_with_S3.rst create mode 100644 doc/source/tracking.rst create mode 100755 setup.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..febbe12 --- /dev/null +++ b/.coverage @@ -0,0 +1,16 @@ +language: python +python: + - "3.5" +install: + - pip install -e . --use-mirrors +before_script: + - pip install -r requirements.txt --use-mirrors + - git clone https://github.com/ccurtis7/diff_classifier.git + - cd ./diff_classifier/diff_classifier/tests/ +script: + - py.test test_features.py --doctest-modules --pep8 coveralls -v --cov coveralls --cov-report term-missing + - py.test test_imagej.py --doctest-modules --pep8 coveralls -v --cov coveralls --cov-report term-missing + - py.test test_msd.py --doctest-modules --pep8 coveralls -v --cov coveralls --cov-report term-missing + - py.test test_utils.py --doctest-modules --pep8 coveralls -v --cov coveralls --cov-report term-missing +after_success: + - coveralls diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..03af03e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[run] +branch = True +source = diff_classifier/* +include = diff_classifier/* +omit = */setup.py +[report] +include = diff_classifier/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3320a2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +diff_classifier/__pycache__ +*.csv +*.tif +diff_classifier/notebooks/.ipynb_checkpoints/* +*.cache +diff_classifier/tests/__pycache__ +*-checkpoint.ipynb +*.pyc +*.egg-info +notebooks/cloudknot_docker* +*.swp +*.png +*.travis.yml +*.DS_store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..30ebd19 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing + +Contributions are welcome, and they are greatly appreciated! Every little bit +helps, and credit will always be given. + +You can contribute in many ways: + +## Types of Contributions + +### Report Bugs + +Report bugs at https://github.com/ccurtis7/diff_classifier/issues. + +If you are reporting a bug, please include: + +- Your operating system name and version. +- Any details about your local setup that might be helpful in troubleshooting. +- Detailed steps to reproduce the bug. + +### Work on "good first issues" + +Look through the GitHub issues for anything labelled "good first issue." These +are issues that we think would be especially appropriate for those new to +open-source software contribution. + +### Fix Bugs + +Look through the GitHub issues for bugs. Anything tagged with "bug" and "help +wanted" is open to whoever wants to implement it. + +### Implement Features + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +When submitting a new feature, please use a [Pull Request](https://scikit-learn.org/dev/developers/contributing.html#how-to-contribute) with a title and description +of the proposed use case. Ideally, new features will include + +- Docstrings implemented with the [Numpy format](https://www.numpy.org/devdocs/docs/howto_document.html) +- Unit tests (see examples [here](https://github.com/ccurtis7/diff_classifier/tree/master/diff_classifier/tests)) + +### Write Documentation + +Diff_classifier could always use more documentation, whether as part of the +official afq-insight docs, in docstrings, or even on the web in blog posts, +articles, and such. + +### Submit Feedback + +We appreciate any and all feedback in your own implementations of diff_classifier :) + +The best way to send feedback is to file an issue at +https://github.com/ccurtis7/diff_classifier/issues. + + + +If you are proposing a feature: + +- Explain in detail how it would work. +- Keep the scope as narrow as possible, to make it easier to implement. +- Remember that this is a volunteer-driven project, and that contributions + are welcome :) diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..b953901 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 ccurtis7 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..dae5140 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +## diff_classifier +[![status](http://joss.theoj.org/papers/003901de75c26c1dd3f060043249bc4f/status.svg)](http://joss.theoj.org/papers/003901de75c26c1dd3f060043249bc4f) +[![Build Status](https://travis-ci.org/ccurtis7/diff_classifier.svg?branch=master)](https://travis-ci.org/ccurtis7/diff_classifier) +[![Coverage Status](https://coveralls.io/repos/github/ccurtis7/diff_classifier/badge.svg)](https://coveralls.io/github/ccurtis7/diff_classifier) +[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/ccurtis7/diff_classifier.svg)](https://scrutinizer-ci.com/g/ccurtis7/diff_classifier/?branch=master) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/ccurtis7/diff_classifier.svg)](http://isitmaintained.com/project/ccurtis7/diff_classifier "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/ccurtis7/diff_classifier.svg)](http://isitmaintained.com/project/ccurtis7/diff_classifier "Percentage of issues still open") +[![GitHub license]( +https://img.shields.io/github/license/ccurtis7/diff_classifier.svg)](https://github.com/ccurtis7/diff_classifier/blob/master/LICENSE) +[![DOI](https://zenodo.org/badge/116980257.svg)](https://zenodo.org/badge/latestdoi/116980257) + + +diff_classifier is a python package for analyzing and visualizing 2D +nanoparticle trajectory data from multi-particle tracking analysis. The package +utilizes the ImageJ package Trackmate for tracking analysis, and Cloudknot for +parallelization on AWS. + +

+ + +This is the diff_classifier development site. You can view the source code and +file new issues. If you are just getting started, you should look at the +[diff_classifier documentation](https://Nance-Lab.github.io/diff_classifier/) + +## Contributing + +Contributions are welcome! Diff_classifier is open source, built on open source, +and we love any input, suggestions, and problems. + +[Guidelines](CONTRIBUTING.md) for contributing are included for your convenience. + +## Credits + +This package was created with [shablona](https://github.com/uwescience/shablona). + +Guidelines for contributions were based off the +[CONTRIBUTIONS](https://github.com/richford/cloudknot/blob/master/CONTRIBUTING.md) +file developed by [Adam Richie-Halford](https://github.com/richford). diff --git a/diff_classifier/__init__.py b/diff_classifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/diff_classifier/aws.py b/diff_classifier/aws.py new file mode 100644 index 0000000..0c62b31 --- /dev/null +++ b/diff_classifier/aws.py @@ -0,0 +1,85 @@ +"""IO functions for downloading and uploading files from AWS S3 buckets. + +The diff_classifier module was built to be used with conjunction with AWS +services. With the exception of Cloudknot parallelization capabilities, most +functions can be used separate from AWS. These functions faciliate interaction +with files stores in S3 buckets. Users must have appropriate credentials to +access desired S3 buckets. + +""" +import os +import os.path as op + +import boto3 + + +# import diff_classifier.imagej as ij + + +def download_s3(remote_fname, local_fname, bucket_name="ccurtis.data"): + """Download a file from S3 to local file-system + + Parameters + ---------- + remote_fname: string + Name of remote file in S3 bucket. + local_fname: string + Desired name to be stored on local computer. + bucket_name: string + Bucket name on S3. + + """ + if not os.path.exists(local_fname): + sthree = boto3.resource('s3') + buckt = sthree.Bucket(bucket_name) + buckt.download_file(remote_fname, local_fname) + + +def upload_s3(local_fname, remote_fname, bucket_name="ccurtis.data"): + """ + Upload a file from local file-system to S3. + + Parameters + ---------- + local_fname: string + Name of local file stored on computer. + remote_fname: string + Desired name to be stored in S3 bucket. + bucket_name: string + Bucket name on S3. + + """ + + sthree = boto3.resource('s3') + buckt = sthree.Bucket(bucket_name) + buckt.upload_file(local_fname, remote_fname) + + +# def partition_and_store(remote_fname, local_dir, bucket_name="ccurtis7.pup"): +# """ +# Download image from S3, partition, and upload partitions to S3. + +# Parameters +# ---------- +# remote_fname: string +# Target filename in S3 bucket. +# local_dir: string +# Local directory to store downloaded file. +# bucket_name: string +# Bucket name on S3. + +# Returns +# ------- +# remote_names: list of strings. +# Names of partitioned images in S3. +# """ +# remote_dir, remote_file = op.split(remote_fname) +# download_s3(remote_fname, op.join(local_dir, remote_file)) +# names = ij.partition_im(op.join(local_dir, remote_file)) + +# remote_names = [] +# for file in names: +# local_file = op.split(file)[1] +# upload_s3(op.join(local_dir, local_file), op.join(remote_dir, local_file)) +# remote_names.append(op.join(remote_dir, local_file)) +# return remote_names diff --git a/diff_classifier/due.py b/diff_classifier/due.py new file mode 100644 index 0000000..64c19ca --- /dev/null +++ b/diff_classifier/due.py @@ -0,0 +1,74 @@ +# emacs: at the end of the file +# ex: set sts=4 ts=4 sw=4 et: +# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### # +""" + +Stub file for a guaranteed safe import of duecredit constructs: if duecredit +is not available. + +To use it, place it into your project codebase to be imported, e.g. copy as + + cp stub.py /path/tomodule/module/due.py + +Note that it might be better to avoid naming it duecredit.py to avoid shadowing +installed duecredit. + +Then use in your code as + + from .due import due, Doi, BibTeX + +See https://github.com/duecredit/duecredit/blob/master/README.md for examples. + +Origin: Originally a part of the duecredit +Copyright: 2015-2016 DueCredit developers +License: BSD-2 +""" +from __future__ import absolute_import, division, print_function + +__version__ = '0.0.5' + + +class InactiveDueCreditCollector(object): + """Just a stub at the Collector which would not do anything""" + def _donothing(self, *args, **kwargs): + """Perform no good and no bad""" + pass + + def dcite(self, *args, **kwargs): + """If I could cite I would""" + def nondecorating_decorator(func): + return func + return nondecorating_decorator + + cite = load = add = _donothing + + def __repr__(self): + return self.__class__.__name__ + '()' + + +def _donothing_func(*args, **kwargs): + """Perform no good and no bad""" + pass + + +try: + from duecredit import due, BibTeX, Doi, Url + if 'due' in locals() and not hasattr(due, 'cite'): + raise RuntimeError( + "Imported due lacks .cite. DueCredit is now disabled") +except Exception as e: + if type(e).__name__ != 'ImportError': + import logging + logging.getLogger("duecredit").error( + "Failed to import duecredit due to %s" % str(e)) + # Initiate due stub + due = InactiveDueCreditCollector() + BibTeX = Doi = Url = _donothing_func + +# Emacs mode definitions +# Local Variables: +# mode: python +# py-indent-offset: 4 +# tab-width: 4 +# indent-tabs-mode: nil +# End: diff --git a/diff_classifier/features.py b/diff_classifier/features.py new file mode 100644 index 0000000..a1949bf --- /dev/null +++ b/diff_classifier/features.py @@ -0,0 +1,901 @@ +"""Functions to calculate trajectory features from input trajectory data + +This module provides functions to calculate trajectory features based off the +ImageJ plugin TrajClassifer by Thorsten Wagner. See details at +https://imagej.net/TraJClassifier. + +""" + +import math +import struct + +import pandas as pd +import numpy as np +import numpy.linalg as LA +import numpy.ma as ma +from scipy.optimize import curve_fit +import matplotlib.pyplot as plt +import diff_classifier.msd as msd + + +def unmask_track(track): + """Removes empty frames from inpute trajectory datset. + + Parameters + ---------- + track : pandas.core.frame.DataFrame + At a minimum, must contain a Frame, Track_ID, X, Y, MSDs, and + Gauss column. + + Returns + ------- + comp_track : pandas.core.frame.DataFrame + Similar to track, but has all masked components removed. + + """ + xpos = ma.masked_invalid(track['X']) + msds = ma.masked_invalid(track['MSDs']) + x_mask = ma.getmask(xpos) + msd_mask = ma.getmask(msds) + comp_frame = ma.compressed(ma.masked_where(msd_mask, track['Frame'])) + compid = ma.compressed(ma.masked_where(msd_mask, track['Track_ID'])) + comp_x = ma.compressed(ma.masked_where(x_mask, track['X'])) + comp_y = ma.compressed(ma.masked_where(x_mask, track['Y'])) + comp_msd = ma.compressed(ma.masked_where(msd_mask, track['MSDs'])) + comp_gauss = ma.compressed(ma.masked_where(msd_mask, track['Gauss'])) + comp_qual = ma.compressed(ma.masked_where(x_mask, track['Quality'])) + comp_snr = ma.compressed(ma.masked_where(x_mask, track['SN_Ratio'])) + comp_meani = ma.compressed(ma.masked_where(x_mask, + track['Mean_Intensity'])) + + data1 = {'Frame': comp_frame, + 'Track_ID': compid, + 'X': comp_x, + 'Y': comp_y, + 'MSDs': comp_msd, + 'Gauss': comp_gauss, + 'Quality': comp_qual, + 'SN_Ratio': comp_snr, + 'Mean_Intensity': comp_meani + } + comp_track = pd.DataFrame(data=data1) + return comp_track + + +def alpha_calc(track): + """Calculates alpha, the exponential fit parameter for MSD data + + Parameters + ---------- + track : pandas.core.frame.DataFrame + At a minimum, must contain a Frames and a MSDs column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + + Returns + ------- + alph : numpy.float64 + The anomalous exponent derived by fitting MSD values to the function, + = 4*dcoef*(n*delt)**alph + dcoef : numpy.float64 + The fitted diffusion coefficient derived by fitting MSD values to the + function above. + + Examples + -------- + >>> frames = 5 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> alpha_calc(dframe) + (2.0000000000000004, 0.4999999999999999) + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames)+3), + ... 'Y': np.cos(np.linspace(1, frames, frames)+3)} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> alpha_calc(dframe) + (0.023690002018364065, 0.5144436515510022) + + """ + + ypos = track['MSDs'] + xpos = track['Frame'] + + def msd_alpha(xpos, alph, dcoef): + return 4*dcoef*(xpos**alph) + + try: + popt, pcov = curve_fit(msd_alpha, xpos, ypos) + alph = popt[0] + dcoef = popt[1] + except RuntimeError: + print('Optimal parameters not found. Print NaN instead.') + alph = np.nan + dcoef = np.nan + return alph, dcoef + + +def gyration_tensor(track): + """Calculates the eigenvalues and eigenvectors of the gyration tensor of the + input trajectory. + + Parameters + ---------- + track : pandas DataFrame + At a minimum, must contain an X and Y column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + + Returns + ------- + eig1 : numpy.float64 + Dominant eigenvalue of the gyration tensor. + eig2 : numpy.float64 + Secondary eigenvalue of the gyration tensor. + eigv1 : numpy.ndarray + Dominant eigenvector of the gyration tensor. + eigv2 : numpy.ndarray + Secondary eigenvector of the gyration tensor. + + Examples + -------- + >>> frames = 5 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> gyration_tensor(dframe) + (4.0, + 4.4408920985006262e-16, + array([ 0.70710678, -0.70710678]), + array([ 0.70710678, 0.70710678])) + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames)+3), + ... 'Y': np.cos(np.linspace(1, frames, frames)+3)} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> gyration_tensor(dframe) + (0.53232560128104522, + 0.42766829138901619, + array([ 0.6020119 , -0.79848711]), + array([-0.79848711, -0.6020119 ])) + + """ + + dframe = track + assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\ + dataframe." + assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\ + X column." + assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\ + Y column." + assert dframe.shape[0] > 0, "track must not be empty." + + matrixa = np.sum((dframe['X'] - np.mean( + dframe['X']))**2)/dframe['X'].shape[0] + matrixb = np.sum((dframe['Y'] - np.mean( + dframe['Y']))**2)/dframe['Y'].shape[0] + matrixab = np.sum((dframe['X'] - np.mean( + dframe['X']))*(dframe['Y'] - np.mean( + dframe['Y'])))/dframe['X'].shape[0] + + eigvals, eigvecs = LA.eig(np.array([[matrixa, matrixab], + [matrixab, matrixb]])) + dom = np.argmax(np.abs(eigvals)) + rec = np.argmin(np.abs(eigvals)) + eig1 = eigvals[dom] + eig2 = eigvals[rec] + eigv1 = eigvecs[dom] + eigv2 = eigvecs[rec] + return eig1, eig2, eigv1, eigv2 + + +def kurtosis(track): + """Calculates the kurtosis of input track. + + Parameters + ---------- + track : pandas.core.frame.DataFrame + At a minimum, must contain an X and Y column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + + Returns + ------- + kurt : numpy.float64 + Kurtosis of the input track. Calculation based on projected 2D + positions on the dominant eigenvector of the radius of gyration tensor. + + Examples + -------- + >>> frames = 5 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> kurtosis(dframe) + 2.5147928994082829 + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames)+3), + ... 'Y': np.cos(np.linspace(1, frames, frames)+3)} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> kurtosis(dframe) + 1.8515139698652476 + + """ + + dframe = track + assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\ + dataframe." + assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\ + X column." + assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\ + Y column." + assert dframe.shape[0] > 0, "track must not be empty." + + eig1, eig2, eigv1, eigv2 = gyration_tensor(dframe) + projection = dframe['X']*eigv1[0] + dframe['Y']*eigv1[1] + + kurt = np.mean((projection - np.mean( + projection))**4/(np.std(projection)**4)) + + return kurt + + +def asymmetry(track): + """Calculates the asymmetry of the trajectory. + + Parameters + ---------- + track : pandas DataFrame + At a minimum, must contain an X and Y column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + + Returns + ------- + eig1 : numpy.float64 + Dominant eigenvalue of the gyration tensor. + eig2 : numpy.float64 + Secondary eigenvalue of the gyration tensor. + asym1 : numpy.float64 + asymmetry of the input track. Equal to 0 for circularly symmetric + tracks, and 1 for linear tracks. + asym2 : numpy.float64 + alternate definition of asymmetry. Equal to 1 for circularly + symmetric tracks, and 0 for linear tracks. + asym3 : numpy.float64 + alternate definition of asymmetry. + + Examples + -------- + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> asymmetry(dframe) + (16.5, 0.0, 1.0, 0.0, 0.69314718055994529) + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames)+3), + ... 'Y': np.cos(np.linspace(1, frames, frames)+3)} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> asymmetry(dframe) + (0.53232560128104522, + 0.42766829138901619, + 0.046430119259539708, + 0.80339606128247354, + 0.0059602683290953052) + + """ + dframe = track + assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\ + dataframe." + assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\ + X column." + assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\ + Y column." + assert dframe.shape[0] > 0, "track must not be empty." + + eig1, eig2, eigv1, eigv2 = gyration_tensor(track) + asym1 = (eig1**2 - eig2**2)**2/(eig1**2 + eig2**2)**2 + asym2 = eig2/eig1 + asym3 = -np.log(1-((eig1-eig2)**2)/(2*(eig1+eig2)**2)) + + return eig1, eig2, asym1, asym2, asym3 + + +def minboundrect(track): + """Calculates the minimum bounding rectangle of an input trajectory. + + Parameters + ---------- + dframe : pandas.core.frame.DataFrame + At a minimum, must contain an X and Y column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + + Returns + ------- + rot_angle : numpy.float64 + Angle of rotation of the bounding box. + area : numpy.float64 + Area of the bounding box. + width : numpy.float64 + Width of the bounding box. + height : numpy.float64 + Height of the bounding box. + center_point : numpy.ndarray + Center point of the bounding box. + corner_pts : numpy.ndarray + Corner points of the bounding box. + + Examples + -------- + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> minboundrect(dframe) + (-2.3561944901923448, + 2.8261664256307952e-14, + 12.727922061357855, + 2.2204460492503131e-15, + array([ 10.5, 8.5]), + array([[ 6., 4.], + [ 15., 13.], + [ 15., 13.], + [ 6., 4.]])) + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames))+3, + ... 'Y': np.cos(np.linspace(1, frames, frames))+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> minboundrect(dframe) + (0.78318530717958657, + 3.6189901131223992, + 1.9949899732081091, + 1.8140392491811692, + array([ 3.02076903, 2.97913884]), + array([[ 4.3676025 , 3.04013439], + [ 2.95381341, 1.63258851], + [ 1.67393557, 2.9181433 ], + [ 3.08772466, 4.32568917]])) + + Notes + ----- + Based off of code from the following repo: + https://github.com/dbworth/minimum-area-bounding-rectangle/blob/master/ + python/min_bounding_rect.py + + """ + + dframe = track + assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\ + dataframe." + assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\ + X column." + assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\ + Y column." + assert dframe.shape[0] > 0, "track must not be empty." + + df2 = np.zeros((dframe.shape[0]+1, 2)) + df2[:-1, :] = dframe[['X', 'Y']].values + df2[-1, :] = dframe[['X', 'Y']].values[0, :] + hull_points_2d = df2 + + edges = np.zeros((len(hull_points_2d)-1, 2)) + + for i in range(len(edges)): + edge_x = hull_points_2d[i+1, 0] - hull_points_2d[i, 0] + edge_y = hull_points_2d[i+1, 1] - hull_points_2d[i, 1] + edges[i] = [edge_x, edge_y] + + edge_angles = np.zeros((len(edges))) + + for i in range(len(edge_angles)): + edge_angles[i] = math.atan2(edges[i, 1], edges[i, 0]) + edge_angles = np.unique(edge_angles) + + start_area = 2 ** (struct.Struct('i').size * 8 - 1) - 1 + min_bbox = (0, start_area, 0, 0, 0, 0, 0, 0) + for i in range(len(edge_angles)): + rads = np.array([[math.cos(edge_angles[i]), + math.cos(edge_angles[i]-(math.pi/2))], + [math.cos(edge_angles[i]+(math.pi/2)), + math.cos(edge_angles[i])]]) + + rot_points = np.dot(rads, np.transpose(hull_points_2d)) + + min_x = np.nanmin(rot_points[0], axis=0) + max_x = np.nanmax(rot_points[0], axis=0) + min_y = np.nanmin(rot_points[1], axis=0) + max_y = np.nanmax(rot_points[1], axis=0) + + width = max_x - min_x + height = max_y - min_y + area = width*height + + if area < min_bbox[1]: + min_bbox = (edge_angles[i], area, width, height, + min_x, max_x, min_y, max_y) + + angle = min_bbox[0] + rads = np.array([[math.cos(angle), math.cos(angle-(math.pi/2))], + [math.cos(angle+(math.pi/2)), math.cos(angle)]]) + + min_x = min_bbox[4] + max_x = min_bbox[5] + min_y = min_bbox[6] + max_y = min_bbox[7] + + center_x = (min_x + max_x)/2 + center_y = (min_y + max_y)/2 + center_point = np.dot([center_x, center_y], rads) + + corner_pts = np.zeros((4, 2)) + corner_pts[0] = np.dot([max_x, min_y], rads) + corner_pts[1] = np.dot([min_x, min_y], rads) + corner_pts[2] = np.dot([min_x, max_y], rads) + corner_pts[3] = np.dot([max_x, max_y], rads) + + return (angle, min_bbox[1], min_bbox[2], min_bbox[3], + center_point, corner_pts) + + +def aspectratio(track): + """Calculates the aspect ratio of the rectangle containing the input track. + + Parameters + ---------- + track : pandas.core.frame.DataFrame + At a minimum, must contain an X and Y column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + + Returns + ------- + aspratio : numpy.float64 + aspect ratio of the trajectory. Always >= 1. + elong : numpy.float64 + elongation of the trajectory. A transformation of the aspect ratio + given by 1 - aspratio**-1. + + Examples + -------- + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> aspectratio(dframe) + (5732146505273195.0, 0.99999999999999978) + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames))+3, + ... 'Y': np.cos(np.linspace(1, frames, frames))+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> aspectratio(dframe) + (1.0997501702946164, 0.090702573174318291) + + """ + + dframe = track + assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\ + dataframe." + assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\ + X column." + assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\ + Y column." + assert dframe.shape[0] > 0, "track must not be empty." + + rangle, area, width, height, center_point, corner_pts = minboundrect(track) + aspratio = width/height + if aspratio > 1: + aspratio = aspratio + else: + aspratio = 1/aspratio + elong = 1 - (1/aspratio) + + return aspratio, elong, center_point + + +def boundedness(track, framerate=1): + """Calculates the boundedness, fractal dimension, and trappedness of the + input track. + + Parameters + ---------- + track : pandas.core.frame.DataFrame + At a minimum, must contain a Frames and a MSDs column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + framerate : framrate of the video being analyzed. Actually cancels out. So + why did I include this. Default is 1. + + Returns + ------- + bound : float + Boundedness of the input track. Quantifies how much a particle with + diffusion coefficient dcoef is restricted by a circular confinement of + radius rad when it diffuses for a time duration N*delt. Defined as + bound = dcoef*N*delt/rad**2. For this case, dcoef is the short time + diffusion coefficient (after 2 frames), and rad is half the maximum + distance between any two positions. + fractd : float + The fractal path dimension defined as fractd = log(N)/log(N*data1*l**-1) + where netdisp is the total length (sum over all steplengths), N is the + number of steps, and data1 is the largest distance between any two + positions. + probf : float + The probability that a particle with diffusion coefficient dcoef and + traced for a period of time N*delt is trapped in region r0. Given by + pt = 1 - exp(0.2048 - 0.25117*(dcoef*N*delt/r0**2)). For this case, + dcoef is the short time diffusion coefficient, and r0 is half the + maximum distance between any two positions. + + Examples + -------- + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> boundedness(dframe) + (1.0, 1.0000000000000002, 0.045311337970735499) + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames)+3), + ... 'Y': np.cos(np.linspace(1, frames, frames)+3)} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> boundedness(dframe) + (0.96037058689895005, 2.9989749477908401, 0.03576118370932313) + + """ + + dframe = track + assert isinstance(dframe, pd.core.frame.DataFrame), "track must be a pandas\ + dataframe." + assert isinstance(dframe['X'], pd.core.series.Series), "track must contain\ + X column." + assert isinstance(dframe['Y'], pd.core.series.Series), "track must contain\ + Y column." + assert dframe.shape[0] > 0, "track must not be empty." + + dframe = track + + if dframe.shape[0] > 2: + length = dframe.shape[0] + distance = np.zeros((length, length)) + + for frame in range(0, length-1): + distance[frame, 0:length-frame-1] =\ + (np.sqrt(msd.nth_diff(dframe['X'], frame+1)**2 + + msd.nth_diff(dframe['Y'], frame+1)**2).values) + + netdisp = np.sum((np.sqrt(msd.nth_diff(dframe['X'], 1)**2 + + msd.nth_diff(dframe['Y'], 1)**2).values)) + rad = np.max(distance)/2 + N = dframe['Frame'][dframe['Frame'].shape[0]-1] + fram = N*framerate + dcoef = dframe['MSDs'][2]/(4*fram) + + bound = dcoef*fram/(rad**2) + fractd = np.log(N)/np.log(N*2*rad/netdisp) + probf = 1 - np.exp(0.2048 - 0.25117*(dcoef*fram/(rad**2))) + else: + bound = np.nan + fractd = np.nan + probf = np.nan + + return bound, fractd, probf + + +def efficiency(track): + """Calculates the efficiency and straitness of the input track + + Parameters + ---------- + track : pandas.core.frame.DataFrame + At a minimum, must contain a Frames and a MSDs column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + + Returns + ------- + eff : float + Efficiency of the input track. Relates the sum of squared step + lengths. Based on Helmuth et al. (2007) and defined as: + E = |xpos(N-1)-xpos(0)|**2/SUM(|xpos(i) - xpos(i-1)|**2 + strait : float + Relates the net displacement netdisp to the sum of step lengths and is + defined as: + S = |xpos(N-1)-xpos(0)|/SUM(|xpos(i) - xpos(i-1)| + + Examples + -------- + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> ft.efficiency(dframe) + (9.0, 0.9999999999999999) + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames))+3, + ... 'Y': np.cos(np.linspace(1, frames, frames))+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> ft.efficiency(dframe) + (0.46192924086141945, 0.22655125514290225) + + """ + + dframe = track + length = dframe.shape[0] + num = (msd.nth_diff(dframe['X'], + length-1)**2 + msd.nth_diff(dframe['Y'], + length-1)**2)[0] + num2 = np.sqrt(num) + + den = np.sum(msd.nth_diff(dframe['X'], + 1)**2 + msd.nth_diff(dframe['Y'], 1)**2) + den2 = np.sum(np.sqrt(msd.nth_diff(dframe['X'], + 1)**2 + msd.nth_diff(dframe['Y'], 1)**2)) + + eff = num/den + strait = num2/den2 + return eff, strait + + +def msd_ratio(track, fram1=3, fram2=100): + """Calculates the MSD ratio of the input track at the specified frames. + + Parameters + ---------- + track : pandas.core.frame.DataFrame + At a minimum, must contain a Frames and a MSDs column. The function + msd_calc can be used to generate the correctly formatted pd dataframe. + fram1 : int + First frame at which to calculate the MSD ratio. + fram2 : int + Last frame at which to calculate the MSD ratio. + + Returns + ------- + ratio: numpy.float64 + MSD ratio as defined by + [MSD(fram1)/MSD(fram2)] - [fram1/fram2] + where fram1 < fram2. For Brownian motion, it is 0; for restricted + motion it is < 0. For directed motion it is > 0. + + Examples + -------- + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.linspace(1, frames, frames)+5, + ... 'Y': np.linspace(1, frames, frames)+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> ft.msd_ratio(dframe, 1, 9) + -0.18765432098765433 + + >>> frames = 10 + >>> data1 = {'Frame': np.linspace(1, frames, frames), + ... 'X': np.sin(np.linspace(1, frames, frames))+3, + ... 'Y': np.cos(np.linspace(1, frames, frames))+3} + >>> dframe = pd.DataFrame(data=data1) + >>> dframe['MSDs'], dframe['Gauss'] = msd_calc(dframe) + >>> ft.msd_ratio(dframe, 1, 9) + 0.04053708075268797 + + """ + + dframe = track + assert fram1 < fram2, "fram1 must be less than fram2" + ratio = (dframe['MSDs'][fram1]/dframe['MSDs'][fram2]) - ( + dframe['Frame'][fram1]/dframe['Frame'][fram2]) + return ratio + + +def calculate_features(dframe, framerate=1, frame=(10, 100), mean_values=True): + """test test test Calculates multiple features from input MSD dataset and stores in pandas + dataframe. + + Parameters + ---------- + dframe : pandas.core.frame.DataFrame + Output from msd.all_msds2. Must have at a minimum the following + columns: + Track_ID, Frame, X, Y, and MSDs. + framerate : int or float + Framerate of the input videos from which trajectories were calculated. + Required for accurate calculation of some features. Default is 1. + Possibly not required. Ignore if performing all calcuations without + units. + frame : int + Frame at which to calculate Deff + + Returns + ------- + datai: pandas.core.frame.DataFrame + Contains a row for each trajectory in dframe. Holds the following + features of each trajetory: Track_ID, alpha, D_fit, kurtosis, + asymmetry1, asymmetry2, asymmetry3, aspect ratio (AR), elongation, + boundedness, fractal dimension (fractal_dim), trappedness, efficiency, + straightness, MSD ratio, frames, X, and Y. + + Examples + -------- + See example outputs from individual feature functions. + + """ + + # Skeleton of Trajectory features metadata table. + # Builds entry for each unique Track ID. + holder = dframe.Track_ID.unique().astype(float) + die = {'Track_ID': holder, + 'alpha': holder, + 'D_fit': holder, + 'kurtosis': holder, + 'asymmetry1': holder, + 'asymmetry2': holder, + 'asymmetry3': holder, + 'AR': holder, + 'elongation': holder, + 'boundedness': holder, + 'fractal_dim': holder, + 'trappedness': holder, + 'efficiency': holder, + 'straightness': holder, + 'MSD_ratio': holder, + 'frames': holder, + 'X': holder, + 'Y': holder, + 'Quality': holder, + 'Mean_Intensity': holder, + 'SN_Ratio': holder, + 'Deff1': holder, + 'Deff2': holder} + + datai = pd.DataFrame(data=die) + + trackids = dframe.Track_ID.unique() + partcount = trackids.shape[0] + + for particle in range(0, partcount): + single_track_masked =\ + dframe.loc[dframe['Track_ID'] == + trackids[particle]].sort_values(['Track_ID', 'Frame'], + ascending=[ + 1, + 1]).reset_index(drop=True) + single_track = unmask_track(single_track_masked) + (datai['alpha'][particle], + datai['D_fit'][particle]) = alpha_calc(single_track) + datai['kurtosis'][particle] = kurtosis(single_track) + (eig1, eig2, datai['asymmetry1'][particle], + datai['asymmetry2'][particle], + datai['asymmetry3'][particle]) = asymmetry(single_track) + (datai['AR'][particle], datai['elongation'][particle], + (datai['X'][particle], + datai['Y'][particle])) = aspectratio(single_track) + (datai['boundedness'][particle], datai['fractal_dim'][particle], + datai['trappedness'][particle]) = boundedness(single_track, framerate) + (datai['efficiency'][particle], + datai['straightness'][particle]) = efficiency(single_track) + datai['frames'][particle] = single_track.shape[0] + if single_track['Frame'][single_track.shape[0]-2] > 2: + datai['MSD_ratio'][particle] = msd_ratio(single_track, 2, + single_track['Frame'][ + single_track.shape[0]-2]) + else: + datai['MSD_ratio'][particle] = np.nan + + try: + datai['Deff1'][particle] = single_track['MSDs'][frame[0]] / (4*frame[0]) + except: + datai['Deff1'][particle] = np.nan + + try: + datai['Deff2'][particle] = single_track['MSDs'][frame[1]] / (4*frame[1]) + except: + datai['Deff2'][particle] = np.nan + + datai['Mean_Intensity'][particle] = np.nanmean(single_track[ + 'Mean_Intensity'].replace([np.inf, -np.inf], np.nan).dropna(how="all").values) + datai['Quality'][particle] = np.nanmean(single_track[ + 'Quality'].replace([np.inf, -np.inf], np.nan).dropna(how="all").values) + datai['SN_Ratio'][particle] = np.nanmean(single_track[ + 'SN_Ratio'].replace([np.inf, -np.inf], np.nan).dropna(how="all").values) + + if mean_values: + nonnum = ['Track_ID'] + for col in datai.columns: + if col not in nonnum: + datai['Mean ' + col] = np.nan + datai['Std ' + col] = np.nan + + for xrange in range(0, 16): + for yrange in range(0, 16): + bitesize = datai[(datai['X'] >= 128*xrange) & (datai['X'] < 128*(xrange+1)) & + (datai['Y'] >= 128*yrange) & (datai['Y'] < 128*(yrange+1))] + bitesize.replace([np.inf, -np.inf], np.nan) + print(bitesize.shape) + for col in bitesize.columns: + if col not in nonnum and 'Mean' not in col and 'Std' not in col: + datai['Mean '+ col][bitesize.index] = np.nanmean(bitesize[col]) + datai['Std '+ col][bitesize.index] = np.nanstd(bitesize[col]) + + return datai + + +def feature_violin(tgroups, feature='boundedness', + labels=['sample 1', 'sample 2', 'sample 3'], + points=40, ylim=[0, 1], nticks=11): + '''Plots violin plots of features in comparison groups + + Parameters + ---------- + tgroups : dict of pandas.core.frames.DataFrame + Dictionary containing pandas dataframes containing trajectory + features of subgroups to be plotted + feature : string + Feature to be compared + labels : list of strings + Labels of subgroups to be plotted. + points : int + Determines resolution of violin plot + ylim : list of int + Y range of output plot + + ''' + + majorticks = np.linspace(ylim[0], ylim[1], nticks) + to_graph = [] + pos = [] + counter = 1 + for key in tgroups: + to_graph.append(tgroups[key][feature][tgroups[key][feature] < 10000].replace([np.inf, -np.inf], np.nan).dropna().values) + pos.append(counter) + counter = counter + 1 + + def set_axis_style(ax, labels): + ax.get_xaxis().set_tick_params(direction='out') + ax.xaxis.set_ticks_position('bottom') + ax.set_xticks(np.arange(1, len(labels) + 1)) + ax.set_xticklabels(labels) + ax.set_xlim(0.25, len(labels) + 0.75) + + fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(6, 6)) + + axes.violinplot(to_graph, pos, points=points, widths=0.9, + showmeans=True, showextrema=False) + set_axis_style(axes, labels) + axes.tick_params(axis='both', which='major', + labelsize=16) + axes.set_ylim(ylim) + axes.set_yticks(majorticks) + + plt.show() diff --git a/diff_classifier/heatmaps.py b/diff_classifier/heatmaps.py new file mode 100755 index 0000000..bd46881 --- /dev/null +++ b/diff_classifier/heatmaps.py @@ -0,0 +1,524 @@ +import matplotlib as mpl +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from scipy.spatial import Voronoi +import scipy.stats as stats +import os +import os.path as op +from shapely.geometry import Point +from shapely.geometry.polygon import Polygon +import numpy.ma as ma +import matplotlib.cm as cm +import diff_classifier.aws as aws + + +def voronoi_finite_polygons_2d(vor, radius=None): + """ + Reconstruct infinite voronoi regions in a 2D diagram to finite + regions. + + Parameters + ---------- + vor : Voronoi + Input diagram + radius : float, optional + Distance to 'points at infinity'. + + Returns + ------- + regions : list of tuples + Indices of vertices in each revised Voronoi regions. + vertices : list of tuples + Coordinates for revised Voronoi vertices. Same as coordinates + of input vertices, with 'points at infinity' appended to the + end. + + """ + + if vor.points.shape[1] != 2: + raise ValueError("Requires 2D input") + + new_regions = [] + new_vertices = vor.vertices.tolist() + + center = vor.points.mean(axis=0) + if radius is None: + radius = vor.points.ptp().max() + + # Construct a map containing all ridges for a given point + all_ridges = {} + for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices): + all_ridges.setdefault(p1, []).append((p2, v1, v2)) + all_ridges.setdefault(p2, []).append((p1, v1, v2)) + + counter = 0 + for p1, region in enumerate(vor.point_region): + try: + vertices = vor.regions[region] + + if all(v >= 0 for v in vertices): + # finite region + new_regions.append(vertices) + continue + + # reconstruct a non-finite region + ridges = all_ridges[p1] + new_region = [v for v in vertices if v >= 0] + + for p2, v1, v2 in ridges: + if v2 < 0: + v1, v2 = v2, v1 + if v1 >= 0: + # finite ridge: already in the region + continue + + # Compute the missing endpoint of an infinite ridge + + t = vor.points[p2] - vor.points[p1] # tangent + t /= np.linalg.norm(t) + n = np.array([-t[1], t[0]]) # normal + + midpoint = vor.points[[p1, p2]].mean(axis=0) + direction = np.sign(np.dot(midpoint - center, n)) * n + far_point = vor.vertices[v2] + direction * radius + + new_region.append(len(new_vertices)) + new_vertices.append(far_point.tolist()) + + # sort region counterclockwise + vs = np.asarray([new_vertices[v] for v in new_region]) + c = vs.mean(axis=0) + angles = np.arctan2(vs[:, 1] - c[1], vs[:, 0] - c[0]) + new_region = np.array(new_region)[np.argsort(angles)] + + # finish + new_regions.append(new_region.tolist()) + except KeyError: + counter = counter + 1 + # print('Oops {}'.format(counter)) + + return new_regions, np.asarray(new_vertices) + + +def plot_heatmap(prefix, feature='asymmetry1', vmin=0, vmax=1, resolution=512, rows=4, cols=4, + upload=True, dpi=None, figsize=(12, 10), remote_folder = "01_18_Experiment", + bucket='ccurtis.data'): + """ + Plot heatmap of trajectories in video with colors corresponding to features. + + Parameters + ---------- + prefix: string + Prefix of file name to be plotted e.g. features_P1.csv prefix is P1. + feature: string + Feature to be plotted. See features_analysis.py + vmin: float64 + Lower intensity bound for heatmap. + vmax: float64 + Upper intensity bound for heatmap. + resolution: int + Resolution of base image. Only needed to calculate bounds of image. + rows: int + Rows of base images used to build tiled image. + cols: int + Columns of base images used to build tiled images. + upload: boolean + True if you want to upload to s3. + dpi: int + Desired dpi of output image. + figsize: list + Desired dimensions of output image. + + Returns + ------- + + """ + # Inputs + # ---------- + merged_ft = pd.read_csv('features_{}.csv'.format(prefix)) + string = feature + leveler = merged_ft[string] + t_min = vmin + t_max = vmax + ires = resolution + + # Building points and color schemes + # ---------- + zs = ma.masked_invalid(merged_ft[string]) + zs = ma.masked_where(zs <= t_min, zs) + zs = ma.masked_where(zs >= t_max, zs) + to_mask = ma.getmask(zs) + zs = ma.compressed(zs) + + xs = ma.compressed(ma.masked_where(to_mask, merged_ft['X'].astype(int))) + ys = ma.compressed(ma.masked_where(to_mask, merged_ft['Y'].astype(int))) + points = np.zeros((xs.shape[0], 2)) + points[:, 0] = xs + points[:, 1] = ys + vor = Voronoi(points) + + # Plot + # ---------- + fig = plt.figure(figsize=figsize, dpi=dpi) + regions, vertices = voronoi_finite_polygons_2d(vor) + my_map = cm.get_cmap('viridis') + norm = mpl.colors.Normalize(t_min, t_max, clip=True) + mapper = cm.ScalarMappable(norm=norm, cmap=cm.viridis) + + test = 0 + p2 = 0 + counter = 0 + for i in range(0, points.shape[0]-1): + try: + polygon = vertices[regions[p2]] + point1 = Point(points[test, :]) + poly1 = Polygon(polygon) + check = poly1.contains(point1) + if check: + plt.fill(*zip(*polygon), color=my_map(norm(zs[test])), alpha=0.7) + p2 = p2 + 1 + test = test + 1 + else: + p2 = p2 + test = test + 1 + except IndexError: + print('Index mismatch possible.') + + mapper.set_array(10) + plt.colorbar(mapper) + plt.xlim(0, ires*cols) + plt.ylim(0, ires*rows) + plt.axis('off') + + print('Plotted {} heatmap successfully.'.format(prefix)) + outfile = 'hm_{}_{}.png'.format(feature, prefix) + fig.savefig(outfile, bbox_inches='tight') + if upload == True: + aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name=bucket) + + +def plot_scatterplot(prefix, feature='asymmetry1', vmin=0, vmax=1, resolution=512, rows=4, cols=4, + dotsize=10, figsize=(12, 10), upload=True, remote_folder = "01_18_Experiment", + bucket='ccurtis.data'): + """ + Plot scatterplot of trajectories in video with colors corresponding to features. + + Parameters + ---------- + prefix: string + Prefix of file name to be plotted e.g. features_P1.csv prefix is P1. + feature: string + Feature to be plotted. See features_analysis.py + vmin: float64 + Lower intensity bound for heatmap. + vmax: float64 + Upper intensity bound for heatmap. + resolution: int + Resolution of base image. Only needed to calculate bounds of image. + rows: int + Rows of base images used to build tiled image. + cols: int + Columns of base images used to build tiled images. + upload: boolean + True if you want to upload to s3. + + """ + # Inputs + # ---------- + merged_ft = pd.read_csv('features_{}.csv'.format(prefix)) + string = feature + leveler = merged_ft[string] + t_min = vmin + t_max = vmax + ires = resolution + + norm = mpl.colors.Normalize(t_min, t_max, clip=True) + mapper = cm.ScalarMappable(norm=norm, cmap=cm.viridis) + + zs = ma.masked_invalid(merged_ft[string]) + zs = ma.masked_where(zs <= t_min, zs) + zs = ma.masked_where(zs >= t_max, zs) + to_mask = ma.getmask(zs) + zs = ma.compressed(zs) + xs = ma.compressed(ma.masked_where(to_mask, merged_ft['X'].astype(int))) + ys = ma.compressed(ma.masked_where(to_mask, merged_ft['Y'].astype(int))) + + fig = plt.figure(figsize=figsize) + plt.scatter(xs, ys, c=zs, s=dotsize) + mapper.set_array(10) + plt.colorbar(mapper) + plt.xlim(0, ires*cols) + plt.ylim(0, ires*rows) + plt.axis('off') + + print('Plotted {} scatterplot successfully.'.format(prefix)) + outfile = 'scatter_{}_{}.png'.format(feature, prefix) + fig.savefig(outfile, bbox_inches='tight') + if upload == True: + aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name=bucket) + + +def plot_trajectories(prefix, resolution=512, rows=4, cols=4, upload=True, + remote_folder = "01_18_Experiment", bucket='ccurtis.data', + figsize=(12, 12), subset=True, size=1000): + """ + Plot trajectories in video. + + Parameters + ---------- + prefix: string + Prefix of file name to be plotted e.g. features_P1.csv prefix is P1. + resolution: int + Resolution of base image. Only needed to calculate bounds of image. + rows: int + Rows of base images used to build tiled image. + cols: int + Columns of base images used to build tiled images. + upload: boolean + True if you want to upload to s3. + + """ + merged = pd.read_csv('msd_{}.csv'.format(prefix)) + particles = int(max(merged['Track_ID'])) + if particles < size: + size = particles - 1 + else: + pass + particles = np.linspace(0, particles, particles-1).astype(int) + if subset: + particles = np.random.choice(particles, size=size, replace=False) + ires = resolution + + fig = plt.figure(figsize=figsize) + for part in particles: + x = merged[merged['Track_ID'] == part]['X'] + y = merged[merged['Track_ID'] == part]['Y'] + plt.plot(x, y, color='k', alpha=0.7) + + plt.xlim(0, ires*cols) + plt.ylim(0, ires*rows) + plt.axis('off') + + print('Plotted {} trajectories successfully.'.format(prefix)) + outfile = 'traj_{}.png'.format(prefix) + fig.savefig(outfile, bbox_inches='tight') + if upload: + aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name=bucket) + + +def plot_histogram(prefix, xlabel='Log Diffusion Coefficient Dist', ylabel='Trajectory Count', + fps=100.02, umppx=0.16, frames=651, y_range=100, frame_interval=20, frame_range=100, + analysis='log', theta='D', upload=True, remote_folder = "01_18_Experiment", + bucket='ccurtis.data'): + """ + Plot heatmap of trajectories in video with colors corresponding to features. + + Parameters + ---------- + prefix: string + Prefix of file name to be plotted e.g. features_P1.csv prefix is P1. + xlabel: string + X axis label. + ylabel: string + Y axis label. + fps: float64 + Frames per second of video. + umppx: float64 + Resolution of video in microns per pixel. + frames: int + Number of frames in video. + y_range: float64 or int + Desire y range of graph. + frame_interval: int + Desired spacing between MSDs/Deffs to be plotted. + analysis: string + Desired output format. If log, will plot log(MSDs/Deffs) + theta: string + Desired output. D for diffusion coefficients. Anything else, MSDs. + upload: boolean + True if you want to upload to s3. + + """ + merged = pd.read_csv('msd_{}.csv'.format(prefix)) + data = merged + frame_range = range(frame_interval, frame_range+frame_interval, frame_interval) + + # load data + + # generate keys for legend + bar = {} + keys = [] + entries = [] + for i in range(0, len(list(frame_range))): + keys.append(i) + entries.append(str(10*frame_interval*(i+1)) + 'ms') + + set_x_limit = False + set_y_limit = True + colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] + fig = plt.figure(figsize=(16, 6)) + + counter = 0 + for i in frame_range: + toi = i/fps + if theta == "MSD": + factor = 1 + else: + factor = 4*toi + + if analysis == 'log': + dist = np.log(umppx*umppx*merged.loc[merged.Frame == i, 'MSDs'].dropna()/factor) + test_bins = np.linspace(-5, 5, 76) + else: + dist = umppx*umppx*merged.loc[merged.Frame == i, 'MSDs'].dropna()/factor + test_bins = np.linspace(0, 20, 76) + + histogram, test_bins = np.histogram(dist, bins=test_bins) + + # Plot_general_histogram_code + avg = np.mean(dist) + + plt.rc('axes', linewidth=2) + plot = histogram + bins = test_bins + width = 0.7 * (bins[1] - bins[0]) + center = (bins[:-1] + bins[1:])/2 + bar[keys[counter]] = plt.bar(center, plot, align='center', width=width, color=colors[counter], label=entries[counter]) + plt.axvline(avg, color=colors[counter]) + plt.xlabel(xlabel, fontsize=30) + plt.ylabel(ylabel, fontsize=30) + plt.tick_params(axis='both', which='major', labelsize=20) + + counter = counter + 1 + if set_y_limit: + plt.gca().set_ylim([0, y_range]) + + if set_x_limit: + plt.gca().set_xlim([0, x_range]) + + plt.legend(fontsize=20, frameon=False) + outfile = 'hist_{}.png'.format(prefix) + fig.savefig(outfile, bbox_inches='tight') + if upload==True: + aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name=bucket) + + +def plot_particles_in_frame(prefix, x_range=600, y_range=2000, upload=True, + remote_folder = "01_18_Experiment", bucket='ccurtis.data'): + """ + Plot number of particles per frame as a function of time. + + Parameters + ---------- + prefix: string + Prefix of file name to be plotted e.g. features_P1.csv prefix is P1. + x_range: float64 or int + Desire x range of graph. + y_range: float64 or int + Desire y range of graph. + upload: boolean + True if you want to upload to s3. + + """ + merged = pd.read_csv('msd_{}.csv'.format(prefix)) + frames = int(max(merged['Frame'])) + framespace = np.linspace(0, frames, frames) + particles = np.zeros((framespace.shape[0])) + for i in range(0, frames): + particles[i] = merged.loc[merged.Frame == i, 'MSDs'].dropna().shape[0] + + fig = plt.figure(figsize=(5, 5)) + plt.plot(framespace, particles, linewidth=4) + plt.xlim(0, x_range) + plt.ylim(0, y_range) + plt.xlabel('Frames', fontsize=20) + plt.ylabel('Particles', fontsize=20) + + outfile = 'in_frame_{}.png'.format(prefix) + fig.savefig(outfile, bbox_inches='tight') + if upload == True: + aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name=bucket) + + +def plot_individual_msds(prefix, x_range=100, y_range=20, umppx=0.16, fps=100.02, alpha=0.1, folder='.', upload=True, + remote_folder="01_18_Experiment", bucket='ccurtis.data', figsize=(10, 10), subset=True, size=1000, + dpi=300): + """ + Plot MSDs of trajectories and the geometric average. + + Parameters + ---------- + prefix: string + Prefix of file name to be plotted e.g. features_P1.csv prefix is P1. + x_range: float64 or int + Desire x range of graph. + y_range: float64 or int + Desire y range of graph. + fps: float64 + Frames per second of video. + umppx: float64 + Resolution of video in microns per pixel. + alpha: float64 + Transparency factor. Between 0 and 1. + upload: boolean + True if you want to upload to s3. + + Returns + ------- + geo_mean: numpy array + Geometric mean of trajectory MSDs at all time points. + geo_SEM: numpy array + Geometric standard errot of trajectory MSDs at all time points. + + """ + + merged = pd.read_csv('{}/msd_{}.csv'.format(folder, prefix)) + + fig = plt.figure(figsize=figsize) + particles = int(max(merged['Track_ID'])) + + if particles < size: + size = particles - 1 + else: + pass + + frames = int(max(merged['Frame'])) + + y = merged['Y'].values.reshape((particles+1, frames+1))*umppx*umppx + x = merged['X'].values.reshape((particles+1, frames+1))/fps +# for i in range(0, particles+1): +# y[i, :] = merged.loc[merged.Track_ID == i, 'MSDs']*umppx*umppx +# x = merged.loc[merged.Track_ID == i, 'Frame']/fps + + particles = np.linspace(0, particles, particles-1).astype(int) + if subset: + particles = np.random.choice(particles, size=size, replace=False) + + y = np.zeros((particles.shape[0], frames+1)) + for idx, val in enumerate(particles): + y[idx, :] = merged.loc[merged.Track_ID == val, 'MSDs']*umppx*umppx + x = merged.loc[merged.Track_ID == val, 'Frame']/fps + plt.plot(x, y[idx, :], 'k', alpha=alpha) + + geo_mean = np.nanmean(ma.log(y), axis=0) + geo_SEM = stats.sem(ma.log(y), axis=0, nan_policy='omit') + plt.plot(x, np.exp(geo_mean), 'k', linewidth=4) + plt.plot(x, np.exp(geo_mean-geo_SEM), 'k--', linewidth=2) + plt.plot(x, np.exp(geo_mean+geo_SEM), 'k--', linewidth=2) + plt.xlim(0, x_range) + plt.ylim(0, y_range) + plt.xlabel('Tau (s)', fontsize=25) + plt.ylabel(r'Mean Squared Displacement ($\mu$m$^2$)', fontsize=25) + + outfile = '{}/msds_{}.png'.format(folder, prefix) + outfile2 = '{}/geomean_{}.csv'.format(folder, prefix) + outfile3 = '{}/geoSEM_{}.csv'.format(folder, prefix) + fig.savefig(outfile, bbox_inches='tight', dpi=dpi) + np.savetxt(outfile2, geo_mean, delimiter=",") + np.savetxt(outfile3, geo_SEM, delimiter=",") + if upload: + aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name=bucket) + aws.upload_s3(outfile2, remote_folder+'/'+outfile2, bucket_name=bucket) + aws.upload_s3(outfile3, remote_folder+'/'+outfile3, bucket_name=bucket) + return geo_mean, geo_SEM diff --git a/diff_classifier/imagej.py b/diff_classifier/imagej.py new file mode 100644 index 0000000..7cf7676 --- /dev/null +++ b/diff_classifier/imagej.py @@ -0,0 +1,426 @@ +"""Tools to perform particle tracking with Trackmate ImageJ plugin. + +This module includes functions for prepping images and performing tracking +using the TrackMate ImageJ plugin [1]_. + +References +---------- +.. [1] Tinevez, JY,; Perry, N. & Schindelin, J. et al. (2016), "TrackMate: an + open and extensible platoform for single-particle tracking.", Methods 115: + 80-90, PMID 27713081 (on Google Scholar). + +""" + +import os +import sys +import subprocess +import tempfile +import random +from itertools import compress + +import os.path as op +import numpy as np +import skimage.io as sio + +import diff_classifier as dc +import diff_classifier.aws as aws + +from sklearn import linear_model +from sklearn import svm + + +def _get_fiji(): + """Checks if Fiji is downloaded. + + Checks if an existing version of Fiji is downloaded and whether + it is identified as the system variable "FIJI_BIN." Only compatible + with Linux and Mac systems. + + """ + home = os.path.expanduser("~") + paths = [ + os.path.join(home, 'Applications/Fiji.app/Contents/MacOS/ImageJ-macosx'), + os.path.join(home, 'Fiji.app/Contents/MacOS/ImageJ-macosx'), + os.path.join(home, 'Fiji.app/ImageJ-linux64') + ] + exists = [os.path.exists(p) for p in paths] + # paths = list(compress(paths, exists)) + + # Currently only works on Mac and Linux + if sys.platform != 'darwin' and not sys.platform.startswith('linux'): + # print('System is not Linux or Mac') + raise ValueError('System is not Linux or Mac') + + # Has the user specified Fiji for us already as an environment variable? + # Want to use it if it is already installed + elif "FIJI_BIN" in os.environ: + print("FIJI_BIN defined.") + return os.environ["FIJI_BIN"] + + # Is Fiji installed somewhere, but not defined in path? + # Checks in two locations, doesn't search entire system + elif any(exists): + print('Exists elsewhere') + counter = 0 + for exist in exists: + if exist: + path = paths[counter] + counter += 1 + return path + + else: + # Download it if not + if sys.platform == 'darwin': + subprocess.run(['wget', 'https://downloads.imagej.net/fiji/latest/fiji-macosx.zip'], cwd=home) + subprocess.run(['unzip', 'fiji-macosx.zip'], cwd=home) + os.environ['FIJI_BIN'] = os.path.join(home, 'Fiji.app/Contents/MacOS/ImageJ-macosx') + + elif sys.platform.startswith('linux'): + subprocess.run(['wget', 'https://downloads.imagej.net/fiji/latest/fiji-linux64.zip'], cwd=home) + subprocess.run(['unzip', 'fiji-linux64.zip'], cwd=home) + os.environ['FIJI_BIN'] = os.path.join(home, 'Fiji.app/ImageJ-linux64') + print("Downloaded Fiji") + return _get_fiji() + + + +def partition_im(tiffname, irows=4, icols=4, ores=(2048, 2048), + ires=(512, 512)): + """Partitions image into smaller images. + + Partitions a large image into irows x icols images of size ires and write + them to the drive. Default input image sizes are from a Nikon/Hamamatsu + camera setup (2048 x 2044 pixels). + + Parameters + ---------- + tiffname : string + Location of input image to be partitioned. + irows : int + Number of rows of size ires pixels to be partitioned from source image. + icols : int + Number of columns of size ires pixels to be partitioned from source image. + ores : tuple of int + Input images are scaled to size ores pixels prior to splitting. + ires : tuple of int + Output images are of size ires pixels. + + Returns + -------- + names : list of str + List of output image filenames + + Examples + -------- + >>> partition_im('your/sample/image.tif', irows=8, icols=8, ires=(256, 256)) + + """ + test = sio.imread(tiffname) + oshape = test.shape + test2 = np.zeros((oshape[0], ores[0], ores[1]), dtype=test.dtype) + test2[0:oshape[0], 0:oshape[1], :] = test + + new_image = np.zeros((oshape[0], ires[0], ires[1]), dtype=test.dtype) + names = [] + + for row in range(irows): + for col in range(icols): + new_image = test2[:, row*ires[0]:(row+1)*ires[0], + col*ires[1]:(col+1)*ires[1]] + current = tiffname.split('.tif')[0] + '_%s_%s.tif' % (row, col) + sio.imsave(current, new_image) + names.append(current) + + return names + + +def mean_intensity(local_im, frame=0): + """Calculates mean intensity of first frame of input image. + + Parameters + ---------- + local_im : string + Location of input image. + frame : int + Frame at which to perform mean. + + Returns + ------- + test_intensity : float + Mean intensity of input image. + + Examples + -------- + >>> mean_intensity('your/sample/image') + + """ + test_image = sio.imread(local_im) + test_intensity = np.mean(test_image[frame, :, :]) + + return test_intensity + + +def track(target, out_file, template=None, fiji_bin=None, + tparams=None): + """Performs particle tracking on input video. + + Particle tracking is performed with the ImageJ plugin Trackmate. Outputs + a csv file containing analysis settings and particle trajectories. + + Parameters + ---------- + target : str + Full path to a tif file to do tracking on. Can also be a URL + (e.g., 'http://fiji.sc/samples/FakeTracks.tif') + out_file : str + Full path to a csv file to store the results. + template : str, optional + The full path of a template for tracking. Defaults to use + `data/trackmate_template.py` stored in the diff_classifier source-code. + fiji_bin : str + The full path to ImageJ executable file. Includes default search + locations for Mac and Linux systems. + radius : float + Estimated radius of particles in image. + threshold : float + Threshold value for particle detection step. + do_median_filtering : bool + If True, performs a median filter on video prior to tracking. + quality : float + Lower quality cutoff value for particle filtering. + xdims : tuple of int + Upper and lower x limits for particle filtering. + ydims : tuple of int + Upper and lower y limits for particle filtering. + median_intensity : float + Lower median intensity cutoff value for particle filtering. + snr : float + Lower signal to noise ratio cutoff value for particle filtering. + limking_max_distance : float + Maximum allowable distance in pixels between two frames to join + particles in track. + gap_closing_max_distance : float + Maximum allowable distance in pixels between more than two frames to + join particles in track. + max_frame_gap : int + Maximum allowable number of frames a particle is allowed to leave video + and be counted as same trajectory. + track_duration : float + Lower duration cutoff in frames for trajectory filtering. + + """ + + tdefault = {'frames': 651, 'radius': 3.0, 'threshold': 0.0, + 'do_median_filtering': False, 'quality': 15.0, + 'xdims': (0, 511), 'ydims': (1, 511), + 'median_intensity': 300.0, + 'snr': 0.0, 'linking_max_distance': 6.0, + 'gap_closing_max_distance': 10.0, + 'max_frame_gap': 3, 'track_duration': 20.0, + 'detector': 'Dog'} + + if tparams is None: + tparams = {} + + for key, value in tdefault.items(): + if key not in tparams: + tparams[key] = value + + if template is None: + template = op.join(op.split(dc.__file__)[0], + 'data', + 'trackmate_template3.py') + + fiji_bin = _get_fiji() + + script = ''.join(open(template).readlines()) + tpfile = tempfile.NamedTemporaryFile(suffix=".py") + fid = open(tpfile.name, 'w') + fid.write(script.format(target_file=target, frames=str(tparams['frames']), + radius=str(tparams['radius']), + threshold=str(tparams['threshold']), + do_median_filtering=str(tparams['do_median_filtering']), + quality=str(tparams['quality']), + xd=str(tparams['xdims'][1]), + yd=str(tparams['ydims'][1]), + ylo=str(tparams['ydims'][0]), + median_intensity=str(tparams['median_intensity']), + snr=str(tparams['snr']), + linking_max_distance=str(tparams['linking_max_distance']), + gap_closing_max_distance=str(tparams['gap_closing_max_distance']), + max_frame_gap=str(tparams['max_frame_gap']), + track_duration=str(tparams['track_duration']), + set_detector=str(tparams['detector']))) + fid.close() + + cmd = "%s --ij2 --headless --run %s" % (fiji_bin, tpfile.name) + print(cmd) + subp = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) + fid = open(out_file, 'w') + fid.write(subp.stdout.decode()) + #print(subp.stdout.decode()) + fid.close() + + +def regress_sys(folder, all_videos, yfit, training_size, randselect=True, + trainingdata=[], frame=0, have_output=True, download=True, + bucket_name='ccurtis.data'): + """Uses regression based on image intensities to select tracking parameters. + + This function uses regression methods from the scikit-learn module to + predict the lower quality cutoff values for particle filtering in TrackMate + based on the intensity distributions of input images. Currently only uses + the first frame of videos for analysis, and is limited to predicting + quality values. + + In practice, users will run regress_sys twice in different modes to build + a regression system. First, set have_output to False. Function will return + list of randomly selected videos to include in the training dataset. The + user should then manually track particles using the Trackmate GUI, and enter + these values in during the next round as the input yfit variable. + + Parameters + ---------- + folder : str + S3 directory containing video files specified in all_videos. + all_videos: list of str + Contains prefixes of video filenames of entire video set to be + tracked. Training dataset will be some subset of these videos. + yfit: numpy.ndarray + Contains manually acquired quality levels using Trackmate for the + files contained in the training dataset. + training_size : int + Number of files in training dataset. + randselect : bool + If True, will randomly select training videos from all_videos. + If False, will use trainingdata as input training dataset. + trainingdata : list of str + Optional manually selected prefixes of video filenames to be + used as training dataset. + have_output: bool + If you have already acquired the quality values (yfit) for the + training dataset, set to True. If False, it will output the files + the user will need to acquire quality values for. + bucket_name : str + S3 bucket containing videos to be downloaded for regression + calculations. + + Returns + ------- + regress_object : list of sklearn.svm.classes. + Contains list of regression objects assembled from the training + datasets. Uses the mean, 10th percentile, 90th percentile, and + standard deviation intensities to predict the quality parameter + in Trackmate. + tprefix : list of str + Contains randomly selected images from all_videos to be included in + training dataset. + + """ + + if randselect: + tprefix = [] + for i in range(0, training_size): + random.seed(i+1) + tprefix.append(all_videos[random.randint(0, len(all_videos))]) + if have_output is False: + print("Get parameters for: {}".format(tprefix[i])) + else: + tprefix = trainingdata + + if have_output is True: + # Define descriptors + descriptors = np.zeros((training_size, 4)) + counter = 0 + for name in tprefix: + local_im = name + '.tif' + remote_im = "{}/{}".format(folder, local_im) + if download: + aws.download_s3(remote_im, local_im, bucket_name=bucket_name) + test_image = sio.imread(local_im) + descriptors[counter, 0] = np.mean(test_image[frame, :, :]) + descriptors[counter, 1] = np.std(test_image[frame, :, :]) + descriptors[counter, 2] = np.percentile(test_image[frame, :, :], 10) + descriptors[counter, 3] = np.percentile(test_image[frame, :, :], 90) + counter = counter + 1 + + # Define regression techniques + xfit = descriptors + classifiers = [ + svm.SVR(), + linear_model.SGDRegressor(), + linear_model.BayesianRidge(), + linear_model.LassoLars(), + linear_model.ARDRegression(), + linear_model.PassiveAggressiveRegressor(), + linear_model.TheilSenRegressor(), + linear_model.LinearRegression()] + + regress_object = [] + for item in classifiers: + clf = item + regress_object.append(clf.fit(xfit, yfit)) + + return regress_object + + else: + return tprefix + + +def regress_tracking_params(regress_object, to_track, + regmethod='LinearRegression', frame=0): + """Predicts quality values to be used in particle tracking. + + Uses the regress object from regress_sys to predict tracking + parameters for TrackMate analysis. + + Parameters + ---------- + regress_object: list of sklearn.svm.classes. + Obtained from regress_sys + to_track: string + Prefix of video files to be tracked. + regmethod: {'LinearRegression', 'SVR', 'SGDRegressor', 'BayesianRidge', + 'LassoLars', 'ARDRegression', 'PassiveAggressiveRegressor', + 'TheilSenRegressor'} + Desired regression method. + + Returns + ------- + fqual: float + Predicted quality factor used in TrackMate analysis. + + """ + + local_im = to_track + '.tif' + pX = np.zeros((1, 4)) + test_image = sio.imread(local_im) + pX[0, 0] = np.mean(test_image[frame, :, :]) + pX[0, 1] = np.std(test_image[frame, :, :]) + pX[0, 2] = np.percentile(test_image[frame, :, :], 10) + pX[0, 3] = np.percentile(test_image[frame:, :, :], 90) + + quality = [] + for item in regress_object: + quality.append(item.predict(pX)[0]) + + if regmethod == 'SVR': + fqual = quality[0] + elif regmethod == 'SGDRegressor': + fqual = quality[1] + elif regmethod == 'BayesianRidge': + fqual = quality[2] + elif regmethod == 'LassoLars': + fqual = quality[3] + elif regmethod == 'ARDRegression': + fqual = quality[4] + elif regmethod == 'PassiveAggressiveRegressor': + fqual = quality[5] + elif regmethod == 'TheilSenRegressor': + fqual = quality[6] + elif regmethod == 'LinearRegression': + fqual = quality[7] + else: + fqual = 3.0 + + return fqual diff --git a/diff_classifier/knotlets.py b/diff_classifier/knotlets.py new file mode 100755 index 0000000..284ce98 --- /dev/null +++ b/diff_classifier/knotlets.py @@ -0,0 +1,465 @@ +'''Functions to submit tracking jobs to AWS Batch with Cloudknot + +This is a set of custom functions for use with Cloutknot for parallelized +multi-particle tracking workflows. These can also be used as template if +users want to build their own parallelized workflows. See Cloudknot +documentation at https://richford.github.io/cloudknot/documentation.html +for more information. + +The base set of functions is split, tracking, and assemble_msds. The split +function splits large images into smaller images that are manageable for a +single EC2 instance. The tracking function tracks nanoparticle trajectories in +a single sub-image from the split function. The assemble_msds function operates +on all sub-image trajectory csv files from the tracking function, calculates +MSDs and features and assembles them into a single msd csv file and a single +features csv file. The workflow looks something like this: + + |-track---| + |-track---| +(image) -split----| |--assemble_msds-----> (msd/feature files) + |-track---| + |-track---| + +''' + + +def split(prefix, remote_folder, bucket, + rows=4, cols=4, ores=(2048, 2048), ires=(512, 512)): + '''Splits input image file into smaller images. + + A function based on imagej.partition_im that download images from an S3 + bucket, splits it into smaller images, and uploads these to S3. Designed to + work with Cloudknot for parallelizable workflows. Typically, this function + is used in conjunction with kn.tracking and kn.assemble_msds for a complete + analysis. + + Parameters + ---------- + prefix : string + Prefix (everything except file extension and folder name) of image file + to be tracked. Must be available on S3. + remote_folder : string + Folder name where file is contained on S3 in the bucket specified by + 'bucket'. + bucket : string + S3 bucket where file is contained. + rows : int + Number of rows to split image into. + cols : int + Number of columns to split image into. + ores : tuple of int + Original resolution of input image. + ires : tuple of int + Resolution of split images. Really just a sanity check to make sure you + correctly splitting. + + ''' + + import os + import boto3 + import diff_classifier.aws as aws + import diff_classifier.imagej as ij + + local_folder = os.getcwd() + filename = '{}.tif'.format(prefix) + remote_name = remote_folder+'/'+filename + local_name = local_folder+'/'+filename + msd_file = 'msd_{}.csv'.format(prefix) + ft_file = 'features_{}.csv'.format(prefix) + aws.download_s3(remote_name, local_name, bucket_name=bucket) + + s3 = boto3.client('s3') + + # Splitting section + names = ij.partition_im(local_name, irows=rows, icols=cols, + ores=ores, ires=ires) + + # Names of subfiles + # names = [] + # for i in range(0, 4): + # for j in range(0, 4): + # names.append('{}_{}_{}.tif'.format(prefix, i, j)) + + for name in names: + aws.upload_s3(name, remote_folder+'/'+name, bucket_name=bucket) + os.remove(name) + print("Done with splitting. Should output file of name {}".format( + remote_folder+'/'+name)) + + os.remove(filename) + + +def tracking(subprefix, remote_folder, bucket, tparams, + regress_f='regress.obj', rows=4, cols=4, ires=(512, 512)): + '''Tracks particles in input image using Trackmate. + + A function based on imagej.track that downloads the image from S3, tracks + particles using Trackmate, and uploads the resulting trajectory file to S3. + Designed to work with Cloudknot for parallelizable workflows. Typically, + this function is used in conjunction with kn.split and kn.assemble_msds for + a complete analysis. + + Parameters + ---------- + subprefix : string + Prefix (everything except file extension and folder name) of image file + to be tracked. Must be available on S3. + remote_folder : string + Folder name where file is contained on S3 in the bucket specified by + 'bucket'. + bucket : string + S3 bucket where file is contained. + regress_f : string + Name of regress object used to predict quality parameter. + rows : int + Number of rows to split image into. + cols : int + Number of columns to split image into. + ires : tuple of int + Resolution of split images. Really just a sanity check to make sure you + correctly splitting. + tparams : dict + Dictionary containing tracking parameters to Trackmate analysis. + + ''' + + import os + import os.path as op + import boto3 + from sklearn.externals import joblib + import diff_classifier.aws as aws + import diff_classifier.utils as ut + import diff_classifier.msd as msd + import diff_classifier.features as ft + import diff_classifier.imagej as ij + + local_folder = os.getcwd() + filename = '{}.tif'.format(subprefix) + remote_name = remote_folder+'/'+filename + local_name = local_folder+'/'+filename + outfile = 'Traj_' + subprefix + '.csv' + local_im = op.join(local_folder, '{}.tif'.format(subprefix)) + row = int(subprefix.split('_')[-2]) + col = int(subprefix.split('_')[-1]) + + aws.download_s3(remote_folder+'/'+regress_f, regress_f, bucket_name=bucket) + with open(regress_f, 'rb') as fp: + regress = joblib.load(fp) + + s3 = boto3.client('s3') + + aws.download_s3('{}/{}'.format(remote_folder, + '{}.tif'.format(subprefix)), + local_im, bucket_name=bucket) + tparams['quality'] = ij.regress_tracking_params(regress, subprefix, + regmethod='PassiveAggressiveRegressor') + + if row == rows-1: + tparams['ydims'] = (tparams['ydims'][0], ires[1] - 27) + + ij.track(local_im, outfile, template=None, fiji_bin=None, + tparams=tparams) + aws.upload_s3(outfile, remote_folder+'/'+outfile, bucket_name=bucket) + print("Done with tracking. Should output file of name {}".format( + remote_folder+'/'+outfile)) + + +def assemble_msds(prefix, remote_folder, bucket, + ires=(512, 512), frames=651): + '''Calculates MSDs and features from input trajectory files + + A function based on msd.all_msds2 and features.calculate_features, creates + msd and feature csv files from input trajectory files and uploads to S3. + Designed to work with Cloudknot for parallelizable workflows. Typically, + this function is used in conjunction with kn.split and kn.tracking for an + entire workflow. + + prefix : string + Prefix (everything except file extension and folder name) of image file + to be tracked. Must be available on S3. + remote_folder : string + Folder name where file is contained on S3 in the bucket specified by + 'bucket'. + bucket : string + S3 bucket where file is contained. + ires : tuple of int + Resolution of split images. Really just a sanity check to make sure you + correctly splitting. + frames : int + Number of frames in input videos. + + ''' + + import os + import boto3 + import diff_classifier.aws as aws + import diff_classifier.msd as msd + import diff_classifier.features as ft + import diff_classifier.utils as ut + + filename = '{}.tif'.format(prefix) + remote_name = remote_folder+'/'+filename + msd_file = 'msd_{}.csv'.format(prefix) + ft_file = 'features_{}.csv'.format(prefix) + + s3 = boto3.client('s3') + + # names = [] + # for i in range(0, 4): + # for j in range(0, 4): + # names.append('{}_{}_{}.tif'.format(prefix, i, j)) + all_objects = s3.list_objects(Bucket=bucket, + Prefix='{}/{}_'.format(remote_folder, + prefix)) + names = [] + rows = 0 + cols = 0 + for entry in all_objects['Contents']: + name = entry['Key'].split('/')[-1] + names.append(name) + row = int(name.split(prefix)[1].split('.')[0].split('_')[-2]) + col = int(name.split(prefix)[1].split('.')[0].split('_')[-1]) + if row > rows: + rows = row + if col > cols: + cols = col + rows = rows + 1 + cols = cols + 1 + + counter = 0 + for name in names: + row = int(name.split(prefix)[1].split('.')[0].split('_')[-2]) + col = int(name.split(prefix)[1].split('.')[0].split('_')[-1]) + + filename = "Traj_{}_{}_{}.csv".format(prefix, row, col) + aws.download_s3(remote_folder+'/'+filename, filename, + bucket_name=bucket) + local_name = filename + + if counter == 0: + to_add = ut.csv_to_pd(local_name) + to_add['X'] = to_add['X'] + ires[0]*col + to_add['Y'] = ires[1] - to_add['Y'] + ires[1]*(rows-1-row) + merged = msd.all_msds2(to_add, frames=frames) + else: + + if merged.shape[0] > 0: + to_add = ut.csv_to_pd(local_name) + to_add['X'] = to_add['X'] + ires[0]*col + to_add['Y'] = ires[1] - to_add['Y'] + ires[1]*(rows-1-row) + to_add['Track_ID'] = to_add['Track_ID' + ] + max(merged['Track_ID']) + 1 + else: + to_add = ut.csv_to_pd(local_name) + to_add['X'] = to_add['X'] + ires[0]*col + to_add['Y'] = ires[1] - to_add['Y'] + ires[1]*(rows-1-row) + to_add['Track_ID'] = to_add['Track_ID'] + + merged = merged.append(msd.all_msds2(to_add, frames=frames)) + print('Done calculating MSDs for row {} and col {}'.format(row, + col)) + counter = counter + 1 + + merged.to_csv(msd_file) + aws.upload_s3(msd_file, remote_folder+'/'+msd_file, bucket_name=bucket) + merged_ft = ft.calculate_features(merged) + merged_ft.to_csv(ft_file) + aws.upload_s3(ft_file, remote_folder+'/'+ft_file, bucket_name=bucket) + + os.remove(ft_file) + os.remove(msd_file) + for name in names: + outfile = 'Traj_' + name.split('.')[0] + '.csv' + os.remove(outfile) + + +def split_track_msds(prefix, remote_folder, bucket, tparams, + rows=4, cols=4, ores=(2048, 2048), ires=(512, 512), + to_split=False, regress_f='regress.obj', frames=651): + '''Splits images, track particles, and calculates MSDs + + A composite function designed to work with Cloudknot to split images, + track particles, and calculate MSDs. + + Parameters + ---------- + prefix : string + Prefix (everything except file extension and folder name) of image file + to be tracked. Must be available on S3. + remote_folder : string + Folder name where file is contained on S3 in the bucket specified by + 'bucket'. + bucket : string + S3 bucket where file is contained. + rows : int + Number of rows to split image into. + cols : int + Number of columns to split image into. + ores : tuple of int + Original resolution of input image. + ires : tuple of int + Resolution of split images. Really just a sanity check to make sure you + correctly splitting. + to_split : bool + If True, will perform image splitting. + regress_f : string + Name of regress object used to predict quality parameter. + frames : int + Number of frames in input videos. + tparams : dict + Dictionary containing tracking parameters to Trackmate analysis. + + ''' + + if to_split: + split(prefix=prefix, remote_folder=remote_folder, bucket=bucket, + rows=rows, cols=cols, ores=ores, ires=ires) + + pref = [] + for row in range(0, rows): + for col in range(0, cols): + pref.append("{}_{}_{}".format(prefix, row, col)) + + for subprefix in pref: + tracking(subprefix=subprefix, remote_folder=remote_folder, bucket=bucket, + regress_f=regress_f, rows=rows, cols=cols, ires=ires, + tparams=tparams) + + assemble_msds(prefix=prefix, remote_folder=remote_folder, bucket=bucket, + ires=ires, frames=frames) + + +# def sensitivity_it(counter): +# '''Performs sensitivity analysis on single input image +# +# An example function (not designed for re-use) of a sensitivity analysis that +# demonstrates the impact of input tracking parameters on output MSDs and +# features. +# +# ''' +# +# import matplotlib as mpl +# mpl.use('Agg') +# import matplotlib.pyplot as plt +# import diff_classifier.aws as aws +# import diff_classifier.utils as ut +# import diff_classifier.msd as msd +# import diff_classifier.features as ft +# import diff_classifier.imagej as ij +# import diff_classifier.heatmaps as hm +# +# from scipy.spatial import Voronoi +# import scipy.stats as stats +# from shapely.geometry import Point +# from shapely.geometry.polygon import Polygon +# import matplotlib.cm as cm +# import os +# import os.path as op +# import numpy as np +# import numpy.ma as ma +# import pandas as pd +# import boto3 +# import itertools +# +# # Sweep parameters +# # ---------------------------------- +# radius = [4.5, 6.0, 7.0] +# do_median_filtering = [True, False] +# quality = [1.5, 4.5, 8.5] +# linking_max_distance = [6.0, 10.0, 15.0] +# gap_closing_max_distance = [6.0, 10.0, 15.0] +# max_frame_gap = [1, 2, 5] +# track_displacement = [0.0, 10.0, 20.0] +# +# sweep = [radius, do_median_filtering, quality, linking_max_distance, +# gap_closing_max_distance, max_frame_gap, track_displacement] +# all_params = list(itertools.product(*sweep)) +# +# # Variable prep +# # ---------------------------------- +# s3 = boto3.client('s3') +# +# folder = '01_18_Experiment' +# s_folder = '{}/sensitivity'.format(folder) +# local_folder = '.' +# prefix = "P1_S1_R_0001_2_2" +# name = "{}.tif".format(prefix) +# local_im = op.join(local_folder, name) +# aws.download_s3('{}/{}/{}.tif'.format(folder, prefix.split('_')[0], prefix), +# '{}.tif'.format(prefix)) +# +# outputs = np.zeros((len(all_params), len(all_params[0])+2)) +# +# # Tracking and calculations +# # ------------------------------------ +# params = all_params[counter] +# outfile = 'Traj_{}_{}.csv'.format(name.split('.')[0], counter) +# msd_file = 'msd_{}_{}.csv'.format(name.split('.')[0], counter) +# geo_file = 'geomean_{}_{}.csv'.format(name.split('.')[0], counter) +# geoS_file = 'geoSEM_{}_{}.csv'.format(name.split('.')[0], counter) +# msd_image = 'msds_{}_{}.png'.format(name.split('.')[0], counter) +# iter_name = "{}_{}".format(prefix, counter) +# +# ij.track(local_im, outfile, template=None, fiji_bin=None, radius=params[0], threshold=0., +# do_median_filtering=params[1], quality=params[2], x=511, y=511, ylo=1, median_intensity=300.0, snr=0.0, +# linking_max_distance=params[3], gap_closing_max_distance=params[4], max_frame_gap=params[5], +# track_displacement=params[6]) +# +# traj = ut.csv_to_pd(outfile) +# msds = msd.all_msds2(traj, frames=651) +# msds.to_csv(msd_file) +# gmean1, gSEM1 = hm.plot_individual_msds(iter_name, alpha=0.05) +# np.savetxt(geo_file, gmean1, delimiter=",") +# np.savetxt(geoS_file, gSEM1, delimiter=",") +# +# aws.upload_s3(outfile, '{}/{}'.format(s_folder, outfile)) +# aws.upload_s3(msd_file, '{}/{}'.format(s_folder, msd_file)) +# aws.upload_s3(geo_file, '{}/{}'.format(s_folder, geo_file)) +# aws.upload_s3(geoS_file, '{}/{}'.format(s_folder, geoS_file)) +# aws.upload_s3(msd_image, '{}/{}'.format(s_folder, msd_image)) +# +# print('Successful parameter calculations for {}'.format(iter_name)) + + +def geomean_msd(prefix, umppx=0.16, fps=100.02, upload=True, + remote_folder="01_18_Experiment", bucket='ccurtis.data', + backup_frames=651): + + import pandas as pd + import numpy as np + import numpy.ma as ma + import diff_classifier.aws as aws + import scipy.stats as stats + + aws.download_s3('{}/msd_{}.csv'.format(remote_folder, prefix), + 'msd_{}.csv'.format(prefix), bucket_name=bucket) + merged = pd.read_csv('msd_{}.csv'.format(prefix)) + try: + particles = int(max(merged['Track_ID'])) + frames = int(max(merged['Frame'])) + ypos = np.zeros((particles+1, frames+1)) + + for i in range(0, particles+1): + ypos[i, :] = merged.loc[merged.Track_ID == i, 'MSDs']*umppx*umppx + xpos = merged.loc[merged.Track_ID == i, 'Frame']/fps + + geo_mean = np.nanmean(ma.log(ypos), axis=0) + geo_stder = ma.masked_equal(stats.sem(ma.log(ypos), axis=0, + nan_policy='omit'), 0.0) + + except ValueError: + geo_mean = np.nan*np.ones(backup_frames) + geo_stder = np.nan*np.ones(backup_frames) + + np.savetxt('geomean_{}.csv'.format(prefix), geo_mean, delimiter=",") + np.savetxt('geoSEM_{}.csv'.format(prefix), geo_stder, delimiter=",") + + if upload: + aws.upload_s3('geomean_{}.csv'.format(prefix), + remote_folder+'/'+'geomean_{}.csv'.format(prefix), + bucket_name=bucket) + aws.upload_s3('geoSEM_{}.csv'.format(prefix), + remote_folder+'/'+'geoSEM_{}.csv'.format(prefix), + bucket_name=bucket) + + return geo_mean, geo_stder \ No newline at end of file diff --git a/diff_classifier/msd.py b/diff_classifier/msd.py new file mode 100644 index 0000000..e5b769c --- /dev/null +++ b/diff_classifier/msd.py @@ -0,0 +1,1046 @@ +"""Functions to calculate mean squared displacements from trajectory data + +This module includes functions to calculate mean squared displacements and +additional measures from input trajectory datasets as calculated by the +Trackmate ImageJ plugin. + +""" +import warnings +import random as rand + +import pandas as pd +import numpy as np +import numpy.ma as ma +import scipy.stats as stats +from scipy import interpolate +import matplotlib.pyplot as plt +from matplotlib.pyplot import cm +import diff_classifier.aws as aws +from scipy.ndimage.morphology import distance_transform_edt as eudist + + +def nth_diff(dataframe, n=1, axis=0): + """Calculates the nth difference between vector elements + + Returns a new vector of size N - n containing the nth difference between + vector elements. + + Parameters + ---------- + dataframe : pandas.core.series.Series of int or float + Input data on which differences are to be calculated. + n : int + Function calculated xpos(i) - xpos(i - n) for all values in pandas + series. + axis : {0, 1} + Axis along which differences are to be calculated. Default is 0. If 0, + input must be a pandas series. If 1, input must be a numpy array. + + Returns + ------- + diff : pandas.core.series.Series of int or float + Pandas series of size N - n, where N is the original size of dataframe. + + Examples + -------- + >>> df = np.ones((5, 10)) + >>> nth_diff(df) + array([[0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + + >>> df = np.ones((5, 10)) + >>> nth_diff (df) + array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) + + """ + + assert isinstance(n, int), "n must be an integer." + + if dataframe.ndim == 1: + length = dataframe.shape[0] + if n <= length: + test1 = dataframe[:-n].reset_index(drop=True) + test2 = dataframe[n:].reset_index(drop=True) + diff = test2 - test1 + else: + diff = np.array([np.nan, np.nan]) + else: + length = dataframe.shape[0] + if n <= length: + if axis == 0: + test1 = dataframe[:-n, :] + test2 = dataframe[n:, :] + else: + test1 = dataframe[:, :-n] + test2 = dataframe[:, n:] + diff = test2 - test1 + else: + diff = np.array([np.nan, np.nan]) + + return diff + + +def msd_calc(track, length=10): + """Calculates mean squared displacement of input track. + + Returns numpy array containing MSD data calculated from an individual track. + + Parameters + ---------- + track : pandas.core.frame.DataFrame + Contains, at a minimum a 'Frame', 'X', and 'Y' column + + Returns + ------- + new_track : pandas.core.frame.DataFrame + Similar to input track. All missing frames of individual trajectories + are filled in with NaNs, and two new columns, MSDs and Gauss are added: + MSDs, calculated mean squared displacements using the formula + MSD = <(xpos-x0)**2> + Gauss, calculated Gaussianity + + Examples + -------- + >>> data1 = {'Frame': [1, 2, 3, 4, 5], + ... 'X': [5, 6, 7, 8, 9], + ... 'Y': [6, 7, 8, 9, 10]} + >>> df = pd.DataFrame(data=data1) + >>> new_track = msd.msd_calc(df, 5) + + >>> data1 = {'Frame': [1, 2, 3, 4, 5], + ... 'X': [5, 6, 7, 8, 9], + ... 'Y': [6, 7, 8, 9, 10]} + >>> df = pd.DataFrame(data=data1) + >>> new_track = msd.msd_calc(df) + + """ + + meansd = np.zeros(length) + gauss = np.zeros(length) + new_frame = np.linspace(1, length, length) + old_frame = track['Frame'] + oldxy = [track['X'], track['Y']] + fxy = [interpolate.interp1d(old_frame, oldxy[0], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[1], bounds_error=False, + fill_value=np.nan)] + + intxy = [ma.masked_equal(fxy[0](new_frame), np.nan), + ma.masked_equal(fxy[1](new_frame), np.nan)] + data1 = {'Frame': new_frame, + 'X': intxy[0], + 'Y': intxy[1] + } + new_track = pd.DataFrame(data=data1) + + for frame in range(0, length-1): + xy = [np.square(nth_diff(new_track['X'], n=frame+1)), + np.square(nth_diff(new_track['Y'], n=frame+1))] + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + meansd[frame+1] = np.nanmean(xy[0] + xy[1]) + gauss[frame+1] = np.nanmean(xy[0]**2 + xy[1]**2 + )/(2*(meansd[frame+1]**2)) + + new_track['MSDs'] = pd.Series(meansd, index=new_track.index) + new_track['Gauss'] = pd.Series(gauss, index=new_track.index) + + return new_track + + +def all_msds(data): + """Calculates mean squared displacements of a trajectory dataset + + Returns numpy array containing MSD data of all tracks in a trajectory + pandas dataframe. + + Parameters + ---------- + data : pandas.core.frame.DataFrame + Contains, at a minimum a 'Frame', 'Track_ID', 'X', and + 'Y' column. Note: it is assumed that frames begins at 1, not 0 with this + function. Adjust before feeding into function. + + Returns + ------- + new_data : pandas.core.frame.DataFrame + Similar to input data. All missing frames of individual trajectories + are filled in with NaNs, and two new columns, MSDs and Gauss are added: + MSDs, calculated mean squared displacements using the formula + MSD = <(xpos-x0)**2> + Gauss, calculated Gaussianity + + Examples + -------- + >>> data1 = {'Frame': [1, 2, 3, 4, 5, 1, 2, 3, 4, 5], + ... 'Track_ID': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2], + ... 'X': [5, 6, 7, 8, 9, 1, 2, 3, 4, 5], + ... 'Y': [6, 7, 8, 9, 10, 2, 3, 4, 5, 6]} + >>> df = pd.DataFrame(data=data1) + >>> all_msds(df) + + """ + + trackids = data.Track_ID.unique() + partcount = trackids.shape[0] + length = int(max(data['Frame'])) + new = {} + new['length'] = partcount*length + new['frame'] = np.zeros(new['length']) + new['ID'] = np.zeros(new['length']) + new['xy'] = [np.zeros(new['length']), + np.zeros(new['length'])] + meansd = np.zeros(new['length']) + gauss = np.zeros(new['length']) + + for particle in range(0, partcount): + single_track = data.loc[data['Track_ID'] == + trackids[particle] + ].sort_values(['Track_ID', 'Frame'], + ascending=[1, 1] + ).reset_index(drop=True) + if particle == 0: + index1 = 0 + index2 = length + else: + index1 = index2 + index2 = index2 + length + new['single_track'] = msd_calc(single_track, length=length) + new['frame'][index1:index2] = np.linspace(1, length, length) + new['ID'][index1:index2] = particle+1 + new['xy'][0][index1:index2] = new['single_track']['X'] + new['xy'][1][index1:index2] = new['single_track']['Y'] + meansd[index1:index2] = new['single_track']['MSDs'] + gauss[index1:index2] = new['single_track']['Gauss'] + + data1 = {'Frame': new['frame'], + 'Track_ID': new['ID'], + 'X': new['xy'][0], + 'Y': new['xy'][1], + 'MSDs': meansd, + 'Gauss': gauss} + new_data = pd.DataFrame(data=data1) + + return new_data + + +def make_xyarray(data, length=651): + """Rearranges xy position data into 2d arrays + + Rearranges xy data from input pandas dataframe into 2D numpy array. + + Parameters + ---------- + data : pd.core.frame.DataFrame + Contains, at a minimum a 'Frame', 'Track_ID', 'X', and + 'Y' column. + length : int + Desired length or number of frames to which to extend trajectories. + Any trajectories shorter than the input length will have the extra space + filled in with NaNs. + + Returns + ------- + xyft : dict of np.ndarray + Dictionary containing xy position data, frame data, and trajectory ID + data. Contains the following keys: + farray, frames data (length x particles) + tarray, trajectory ID data (length x particles) + xarray, x position data (length x particles) + yarray, y position data (length x particles) + + Examples + -------- + >>> data1 = {'Frame': [0, 1, 2, 3, 4, 2, 3, 4, 5, 6], + ... 'Track_ID': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2], + ... 'X': [5, 6, 7, 8, 9, 1, 2, 3, 4, 5], + ... 'Y': [6, 7, 8, 9, 10, 2, 3, 4, 5, 6]} + >>> df = pd.DataFrame(data=data1) + >>> length = max(df['Frame']) + 1 + >>> xyft = msd.make_xyarray(df, length=length) + {'farray': array([[0., 0.], + [1., 1.], + [2., 2.], + [3., 3.], + [4., 4.], + [5., 5.], + [6., 6.]]), + 'tarray': array([[1., 2.], + [1., 2.], + [1., 2.], + [1., 2.], + [1., 2.], + [1., 2.], + [1., 2.]]), + 'xarray': array([[ 5., nan], + [ 6., nan], + [ 7., 1.], + [ 8., 2.], + [ 9., 3.], + [nan, 4.], + 'yarray': [nan, 5.]]), + array([[ 6., nan], + [ 7., nan], + [ 8., 2.], + [ 9., 3.], + [10., 4.], + [nan, 5.], + [nan, 6.]])} + + """ + + # Initial values + first_p = int(min(data['Track_ID'])) + particles = int(max(data['Track_ID'])) - first_p + 1 + xyft = {} + xyft['xarray'] = np.zeros((length, particles)) + xyft['yarray'] = np.zeros((length, particles)) + xyft['farray'] = np.zeros((length, particles)) + xyft['tarray'] = np.zeros((length, particles)) + xyft['qarray'] = np.zeros((length, particles)) + xyft['snarray'] = np.zeros((length, particles)) + xyft['iarray'] = np.zeros((length, particles)) + + track = data[data['Track_ID'] == first_p + ].sort_values(['Track_ID', 'Frame'], + ascending=[1, 1]).reset_index(drop=True) + new_frame = np.linspace(0, length-1, length) + + old_frame = track['Frame'].values.astype(float) + oldxy = [track['X'].values, + track['Y'].values, + track['Quality'].values, + track['SN_Ratio'].values, + track['Mean_Intensity'].values] + fxy = [interpolate.interp1d(old_frame, oldxy[0], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[1], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[2], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[3], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[4], bounds_error=False, + fill_value=np.nan)] + + intxy = [fxy[0](new_frame), fxy[1](new_frame), fxy[2](new_frame), + fxy[3](new_frame), fxy[4](new_frame)] + + # Fill in entire array + xyft['xarray'][:, 0] = intxy[0] + xyft['yarray'][:, 0] = intxy[1] + xyft['farray'][:, 0] = new_frame + xyft['tarray'][:, 0] = first_p + xyft['qarray'][:, 0] = intxy[2] + xyft['snarray'][:, 0] = intxy[3] + xyft['iarray'][:, 0] = intxy[4] + + for part in range(first_p+1, first_p+particles): + track = data[data['Track_ID'] == part + ].sort_values(['Track_ID', 'Frame'], + ascending=[1, 1]).reset_index(drop=True) + + old_frame = track['Frame'] + oldxy = [track['X'].values, + track['Y'].values, + track['Quality'].values, + track['SN_Ratio'].values, + track['Mean_Intensity'].values] + + fxy = [interpolate.interp1d(old_frame, oldxy[0], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[1], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[2], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[3], bounds_error=False, + fill_value=np.nan), + interpolate.interp1d(old_frame, oldxy[4], bounds_error=False, + fill_value=np.nan)] + + intxy = [fxy[0](new_frame), fxy[1](new_frame), fxy[2](new_frame), + fxy[3](new_frame), fxy[4](new_frame)] + + xyft['xarray'][:, part-first_p] = intxy[0] + xyft['yarray'][:, part-first_p] = intxy[1] + xyft['farray'][:, part-first_p] = new_frame + xyft['tarray'][:, part-first_p] = part + xyft['qarray'][:, part-first_p] = intxy[2] + xyft['snarray'][:, part-first_p] = intxy[3] + xyft['iarray'][:, part-first_p] = intxy[4] + + return xyft + + +def all_msds2(data, frames=651): + """Calculates mean squared displacements of input trajectory dataset + + Returns numpy array containing MSD data of all tracks in a trajectory pandas + dataframe. + + Parameters + ---------- + data : pandas.core.frame.DataFrame + Contains, at a minimum a 'Frame', 'Track_ID', 'X', and + 'Y' column. Note: it is assumed that frames begins at 0. + + Returns + ------- + new_data : pandas.core.frame.DataFrame + Similar to input data. All missing frames of individual trajectories + are filled in with NaNs, and two new columns, MSDs and Gauss are added: + MSDs, calculated mean squared displacements using the formula + MSD = <(xpos-x0)**2> + Gauss, calculated Gaussianity + + Examples + -------- + >>> data1 = {'Frame': [0, 1, 2, 3, 4, 0, 1, 2, 3, 4], + ... 'Track_ID': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2], + ... 'X': [5, 6, 7, 8, 9, 1, 2, 3, 4, 5], + ... 'Y': [6, 7, 8, 9, 10, 2, 3, 4, 5, 6]} + >>> df = pd.DataFrame(data=data1) + >>> cols = ['Frame', 'Track_ID', 'X', 'Y', 'MSDs', 'Gauss'] + >>> om flength = max(df['Frame']) + 1 + >>> msd.all_msds2(df, frames=length)[cols] + + """ + if data.shape[0] > 2: + try: + xyft = make_xyarray(data, length=frames) + length = xyft['xarray'].shape[0] + particles = xyft['xarray'].shape[1] + + meansd = np.zeros((length, particles)) + gauss = np.zeros((length, particles)) + + for frame in range(0, length-1): + xpos = np.square(nth_diff(xyft['xarray'], n=frame+1)) + ypos = np.square(nth_diff(xyft['yarray'], n=frame+1)) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + meansd[frame+1, :] = np.nanmean(xpos + ypos, axis=0) + gauss[frame+1, :] = np.nanmean(xpos**2 + ypos**2, axis=0 + )/(2*(meansd[frame+1]**2)) + + data1 = {'Frame': xyft['farray'].flatten('F'), + 'Track_ID': xyft['tarray'].flatten('F'), + 'X': xyft['xarray'].flatten('F'), + 'Y': xyft['yarray'].flatten('F'), + 'MSDs': meansd.flatten('F'), + 'Gauss': gauss.flatten('F'), + 'Quality': xyft['qarray'].flatten('F'), + 'SN_Ratio': xyft['snarray'].flatten('F'), + 'Mean_Intensity': xyft['iarray'].flatten('F')} + + new_data = pd.DataFrame(data=data1) + except ValueError: + data1 = {'Frame': [], + 'Track_ID': [], + 'X': [], + 'Y': [], + 'MSDs': [], + 'Gauss': [], + 'Quality': [], + 'SN_Ratio': [], + 'Mean_Intensity': []} + new_data = pd.DataFrame(data=data1) + except IndexError: + data1 = {'Frame': [], + 'Track_ID': [], + 'X': [], + 'Y': [], + 'MSDs': [], + 'Gauss': [], + 'Quality': [], + 'SN_Ratio': [], + 'Mean_Intensity': []} + new_data = pd.DataFrame(data=data1) + else: + data1 = {'Frame': [], + 'Track_ID': [], + 'X': [], + 'Y': [], + 'MSDs': [], + 'Gauss': [], + 'Quality': [], + 'SN_Ratio': [], + 'Mean_Intensity': []} + new_data = pd.DataFrame(data=data1) + + return new_data + + +def geomean_msdisp(prefix, umppx=0.16, fps=100.02, upload=True, + remote_folder="01_18_Experiment", bucket='ccurtis.data', + backup_frames=651): + """Comptes geometric averages of mean squared displacement datasets + + Calculates geometric averages and stadard errors for MSD datasets. Might + error out if not formatted as output from all_msds2. + + Parameters + ---------- + prefix : string + Prefix of file name to be plotted e.g. features_P1.csv prefix is P1. + umppx : float + Microns per pixel of original images. + fps : float + Frames per second of video. + upload : bool + True if you want to upload to s3. + remote_folder : string + Folder in S3 bucket to upload to. + bucket : string + Name of S3 bucket to upload to. + + Returns + ------- + geo_mean : numpy.ndarray + Geometric mean of trajectory MSDs at all time points. + geo_stder : numpy.ndarray + Geometric standard errot of trajectory MSDs at all time points. + + """ + + merged = pd.read_csv('msd_{}.csv'.format(prefix)) + try: + particles = int(max(merged['Track_ID'])) + frames = int(max(merged['Frame'])) + ypos = np.zeros((particles+1, frames+1)) + + for i in range(0, particles+1): + ypos[i, :] = merged.loc[merged.Track_ID == i, 'MSDs']*umppx*umppx + xpos = merged.loc[merged.Track_ID == i, 'Frame']/fps + + geo_mean = np.nanmean(ma.log(ypos), axis=0) + geo_stder = ma.masked_equal(stats.sem(ma.log(ypos), axis=0, + nan_policy='omit'), 0.0) + + except ValueError: + geo_mean = np.nan*np.ones(backup_frames) + geo_stder = np.nan*np.ones(backup_frames) + + np.savetxt('geomean_{}.csv'.format(prefix), geo_mean, delimiter=",") + np.savetxt('geoSEM_{}.csv'.format(prefix), geo_stder, delimiter=",") + + if upload: + aws.upload_s3('geomean_{}.csv'.format(prefix), + remote_folder+'/'+'geomean_{}.csv'.format(prefix), + bucket_name=bucket) + aws.upload_s3('geoSEM_{}.csv'.format(prefix), + remote_folder+'/'+'geoSEM_{}.csv'.format(prefix), + bucket_name=bucket) + + return geo_mean, geo_stder + + +def binning(experiments, wells=4, prefix='test'): + """Split set of input experiments into groups. + + Parameters + ---------- + experiments : list of str + List of experiment names. + wells : int + Number of groups to divide experiments into. + + Returns + ------- + slices : int + Number of experiments per group. + bins : dict of list of str + Dictionary, keys corresponding to group names, and elements containing + lists of experiments in each group. + bin_names : list of str + List of group names + + """ + + total_videos = len(experiments) + bins = {} + slices = int(total_videos/wells) + bin_names = [] + + for num in range(0, wells): + slice1 = num*slices + slice2 = (num+1)*(slices) + pref = '{}_W{}'.format(prefix, num) + bins[pref] = experiments[slice1:slice2] + bin_names.append(pref) + return slices, bins, bin_names + + +def precision_weight(group, geo_stder): + """Calculates precision-based weights from input standard error data + + Calculates precision weights to be used in precision-averaged MSD + calculations. + + Parameters + ---------- + group : list of str + List of experiment names to average. Each element corresponds to a key + in geo_stder and geomean. + geo_stder : dict of numpy.ndarray + Each entry in dictionary corresponds to the standard errors of an MSD + profile, the key corresponding to an experiment name. + + Returns + ------- + weights: numpy.ndarray + Precision weights to be used in precision averaging. + w_holder : numpy.ndarray + Precision values of each video at each time point. + + """ + + frames = np.shape(geo_stder[group[0]])[0] + slices = len(group) + video_counter = 0 + w_holder = np.zeros((slices, frames)) + for sample in group: + w_holder[video_counter, :] = 1/(geo_stder[sample]*geo_stder[sample]) + video_counter = video_counter + 1 + + w_holder = ma.masked_equal(w_holder, 0.0) + w_holder = ma.masked_equal(w_holder, 1.0) + + weights = ma.sum(w_holder, axis=0) + + return weights, w_holder + + +def precision_averaging(group, geomean, geo_stder, weights, save=True, + bucket='ccurtis.data', folder='test', + experiment='test'): + """Calculates precision-weighted averages of MSD datasets. + + Parameters + ---------- + group : list of str + List of experiment names to average. Each element corresponds to a key + in geo_stder and geomean. + geomean : dict of numpy.ndarray + Each entry in dictionary corresponds to an MSD profiles, they key + corresponding to an experiment name. + geo_stder : dict of numpy.ndarray + Each entry in dictionary corresponds to the standard errors of an MSD + profile, the key corresponding to an experiment name. + weights : numpy.ndarray + Precision weights to be used in precision averaging. + + Returns + ------- + geo : numpy.ndarray + Precision-weighted averaged MSDs from experiments specified in group + geo_stder : numpy.ndarray + Precision-weighted averaged SEMs from experiments specified in group + + """ + + frames = np.shape(geo_stder[group[0]])[0] + slices = len(group) + + video_counter = 0 + geo_holder = np.zeros((slices, frames)) + gstder_holder = np.zeros((slices, frames)) + w_holder = np.zeros((slices, frames)) + for sample in group: + w_holder[video_counter, :] = (1/(geo_stder[sample]*geo_stder[sample]) + )/weights + geo_holder[video_counter, :] = w_holder[video_counter, : + ] * geomean[sample] + gstder_holder[video_counter, :] = 1/(geo_stder[sample]*geo_stder[sample] + ) + video_counter = video_counter + 1 + + w_holder = ma.masked_equal(w_holder, 0.0) + w_holder = ma.masked_equal(w_holder, 1.0) + geo_holder = ma.masked_equal(geo_holder, 0.0) + geo_holder = ma.masked_equal(geo_holder, 1.0) + gstder_holder = ma.masked_equal(gstder_holder, 0.0) + gstder_holder = ma.masked_equal(gstder_holder, 1.0) + + geo = ma.sum(geo_holder, axis=0) + geo_stder = ma.sqrt((1/ma.sum(gstder_holder, axis=0))) + + if save: + geo_f = 'geomean_{}.csv'.format(experiment) + gstder_f = 'geoSEM_{}.csv'.format(experiment) + np.savetxt(geo_f, geo, delimiter=',') + np.savetxt(gstder_f, geo_stder, delimiter=',') + aws.upload_s3(geo_f, '{}/{}'.format(folder, geo_f), bucket_name=bucket) + aws.upload_s3(gstder_f, '{}/{}'.format(folder, gstder_f), + bucket_name=bucket) + + geodata = Bunch(geomean=geo, geostd=geo_stder, weighthold=w_holder, + geostdhold=gstder_holder) + + return geodata + + +def plot_all_experiments(experiments, bucket='ccurtis.data', folder='test', + yrange=(10**-1, 10**1), fps=100.02, + xrange=(10**-2, 10**0), upload=True, + outfile='test.png', exponential=True, + labels=None, log=True): + """Plots precision-weighted averages of MSD datasets. + + Plots pre-calculated precision-weighted averages of MSD datasets calculated + from precision_averaging and stored in an AWS S3 bucket. + + Parameters + ---------- + group : list of str + List of experiment names to plot. Each experiment must have an MSD and + SEM file associated with it in s3. + bucket : str + S3 bucket from which to download data. + folder : str + Folder in s3 bucket from which to download data. + yrange : list of float + Y range of plot + xrange: list of float + X range of plot + upload : bool + True to upload to S3 + outfile : str + Filename of output image + + """ + + n = len(experiments) + + if labels==None: + labels = experiments + + color = iter(cm.viridis(np.linspace(0, 0.9, n))) + + fig = plt.figure(figsize=(8.5, 8.5)) + ax = fig.add_subplot(111) + plt.xlim(xrange[0], xrange[1]) + plt.ylim(yrange[0], yrange[1]) + plt.xlabel('Tau (s)', fontsize=25) + plt.ylabel(r'Mean Squared Displacement ($\mu$m$^2$)', fontsize=25) + + geo = {} + gstder = {} + counter = 0 + for experiment in experiments: + aws.download_s3('{}/geomean_{}.csv'.format(folder, experiment), + 'geomean_{}.csv'.format(experiment), bucket_name=bucket) + aws.download_s3('{}/geoSEM_{}.csv'.format(folder, experiment), + 'geoSEM_{}.csv'.format(experiment), bucket_name=bucket) + + geo[counter] = np.genfromtxt('geomean_{}.csv'.format(experiment)) + gstder[counter] = np.genfromtxt('geoSEM_{}.csv'.format(experiment)) + geo[counter] = ma.masked_equal(geo[counter], 0.0) + gstder[counter] = ma.masked_equal(gstder[counter], 0.0) + + frames = np.shape(gstder[counter])[0] + xpos = np.linspace(0, frames-1, frames)/fps + c = next(color) + + if exponential: + ax.plot(xpos, np.exp(geo[counter]), c=c, linewidth=6, + label=labels[counter]) + ax.fill_between(xpos, np.exp(geo[counter] - 1.96*gstder[counter]), + np.exp(geo[counter] + 1.96*gstder[counter]), + color=c, alpha=0.4) + + else: + ax.plot(xpos, geo[counter], c=c, linewidth=6, + label=labels[counter]) + ax.fill_between(xpos, geo[counter] - 1.96*gstder[counter], + geo[counter] + 1.96*gstder[counter], color=c, + alpha=0.4) + + counter = counter + 1 + + if log: + ax.set_xscale("log") + ax.set_yscale("log") + + plt.legend(frameon=False, loc=2, prop={'size': 16}) + + if upload: + fig.savefig(outfile, bbox_inches='tight') + aws.upload_s3(outfile, folder+'/'+outfile, bucket_name=bucket) + + +def checkerboard_mask(dims=(512, 512), squares=50, width=25): + """Creates a 2D Boolean checkerboard mask + + Creates a Boolean array of evenly spaced squares. + Whitespace is set to True. + + Parameters + ---------- + + dims : tuple of int + Dimensions of desired Boolean array + squares : int + Dimensions of in individual square in array + width : int + Dimension of spacing between squares + + Returns + ---------- + + zeros : numpy.ndarray of bool + 2D Boolean array of evenly spaced squares + + """ + zeros = np.zeros(dims) == 0 + square_d = squares + + loy = width + hiy = loy + square_d + + for j in range(50): + + lox = width + hix = lox + square_d + for i in range(50): + + if hix < 512 and hiy < 512: + zeros[loy:hiy, lox:hix] = False + elif hix < 512: + zeros[loy:512-1, lox:hix] = False + elif hiy < 512: + zeros[loy:hiy, lox:512-1] = False + else: + zeros[loy:512-1, lox:512-1] = False + break + + lox = hix + width + hix = lox + square_d + + loy = hiy + width + hiy = loy + square_d + + return zeros + + +def random_walk(nsteps=100, seed=None, start=(0, 0), step=1, mask=None, + stuckprob=0.5): + """Creates 2d random walk trajectory. + + Parameters + ---------- + nsteps : int + Number of steps for trajectory to move. + seed : int + Seed for pseudo-random number generator for reproducability. + start : tuple of int or float + Starting xy coordinates at which the random walk begins. + step : int or float + Magnitude of single step + mask : numpy.ndarray of bool + Mask of barriers contraining diffusion + stuckprop : float + Probability of "particle" adhering to barrier when it makes contact + + Returns + ------- + x : numpy.ndarray + Array of x coordinates of random walk. + y : numpy.ndarray + Array of y coordinates of random walk. + + """ + + if type(mask) is np.ndarray: + while not mask[start[0], start[1]]: + start = (start[0], start[1]+1) + eumask = eudist(~mask) + + np.random.seed(seed=seed) + + x = np.zeros(nsteps) + y = np.zeros(nsteps) + x[0] = start[0] + y[0] = start[1] + + # Checks to see if a mask is being used first + if not type(mask) is np.ndarray: + for i in range(1, nsteps): + val = rand.randint(1, 4) + if val == 1: + x[i] = x[i - 1] + step + y[i] = y[i - 1] + elif val == 2: + x[i] = x[i - 1] - step + y[i] = y[i - 1] + elif val == 3: + x[i] = x[i - 1] + y[i] = y[i - 1] + step + else: + x[i] = x[i - 1] + y[i] = y[i - 1] - step + else: + # print("Applied mask") + for i in range(1, nsteps): + val = rand.randint(1, 4) + # If mask is being used, checks if entry is in mask or not + if mask[int(x[i-1]), int(y[i-1])]: + if val == 1: + x[i] = x[i - 1] + step + y[i] = y[i - 1] + elif val == 2: + x[i] = x[i - 1] - step + y[i] = y[i - 1] + elif val == 3: + x[i] = x[i - 1] + y[i] = y[i - 1] + step + else: + x[i] = x[i - 1] + y[i] = y[i - 1] - step + # If it does cross into a False area, probability to be stuck + elif np.random.rand() > stuckprob: + x[i] = x[i - 1] + y[i] = y[i - 1] + + while eumask[int(x[i]), int(y[i])] > 0: + vals = np.zeros(4) + vals[0] = eumask[int(x[i] + step), int(y[i])] + vals[1] = eumask[int(x[i] - step), int(y[i])] + vals[2] = eumask[int(x[i]), int(y[i] + step)] + vals[3] = eumask[int(x[i]), int(y[i] - step)] + vali = np.argmin(vals) + + if vali == 0: + x[i] = x[i] + step + y[i] = y[i] + elif vali == 1: + x[i] = x[i] - step + y[i] = y[i] + elif vali == 2: + x[i] = x[i] + y[i] = y[i] + step + else: + x[i] = x[i] + y[i] = y[i] - step + # Otherwise, particle is stuck on "cell" + else: + x[i] = x[i - 1] + y[i] = y[i - 1] + + return x, y + + +# def random_walk(nsteps=100, seed=1, start=(0, 0)): +# """Creates 2d random walk trajectory. +# +# Parameters +# ---------- +# nsteps : int +# Number of steps for trajectory to move. +# seed : int +# Seed for pseudo-random number generator for reproducability. +# start : tuple of int or float +# Starting xy coordinates at which the random walk begins. +# +# Returns +# ------- +# x : numpy.ndarray +# Array of x coordinates of random walk. +# y : numpy.ndarray +# Array of y coordinates of random walk. +# +# """ +# +# rand.seed(a=seed) +# +# x = np.zeros(nsteps) +# y = np.zeros(nsteps) +# x[0] = start[0] +# y[0] = start[1] +# +# for i in range(1, nsteps): +# val = rand.randint(1, 4) +# if val == 1: +# x[i] = x[i - 1] + 1 +# y[i] = y[i - 1] +# elif val == 2: +# x[i] = x[i - 1] - 1 +# y[i] = y[i - 1] +# elif val == 3: +# x[i] = x[i - 1] +# y[i] = y[i - 1] + 1 +# else: +# x[i] = x[i - 1] +# y[i] = y[i - 1] - 1 +# +# return x, y + + +def random_traj_dataset(nframes=100, nparts=30, seed=1, fsize=(0, 512), + ndist=(1, 2)): + """Creates a random population of random walks. + + Parameters + ---------- + nframes : int + Number of frames for each random trajectory. + nparts : int + Number of particles in trajectory dataset. + seed : int + Seed for pseudo-random number generator for reproducability. + fsize : tuple of int or float + Scope of points over which particles may start at. + ndist : tuple of int or float + Parameters to generate normal distribution, mu and sigma. + + Returns + ------- + dataf : pandas.core.frame.DataFrame + Trajectory data containing a 'Frame', 'Track_ID', 'X', and + 'Y' column. + + """ + + frames = [] + trackid = [] + x = [] + y = [] + start = [0, 0] + pseed = seed + + for i in range(nparts): + rand.seed(a=i+pseed) + start[0] = rand.randint(fsize[0], fsize[1]) + rand.seed(a=i+3+pseed) + start[1] = rand.randint(fsize[0], fsize[1]) + rand.seed(a=i+5+pseed) + weight = rand.normalvariate(mu=ndist[0], sigma=ndist[1]) + + trackid = np.append(trackid, np.array([i]*nframes)) + xi, yi = random_walk(nsteps=nframes, seed=i) + x = np.append(x, weight*xi+start[0]) + y = np.append(y, weight*yi+start[1]) + frames = np.append(frames, np.linspace(0, nframes-1, nframes)) + + datai = {'Frame': frames, + 'Track_ID': trackid, + 'X': x, + 'Y': y, + 'Quality': nframes*nparts*[10], + 'SN_Ratio': nframes*nparts*[0.1], + 'Mean_Intensity': nframes*nparts*[120]} + dataf = pd.DataFrame(data=datai) + + return dataf + + +class Bunch: + def __init__(self, **kwds): + self.__dict__.update(kwds) diff --git a/diff_classifier/pca.py b/diff_classifier/pca.py new file mode 100644 index 0000000..4129c66 --- /dev/null +++ b/diff_classifier/pca.py @@ -0,0 +1,787 @@ +"""Performs principle component analysis on input datasets. + +This module performs principle component analysis on input datasets using +functions from scikit-learn. It is optimized to data formats used in +diff_classifier, but can potentially be extended to other applications. + +""" + +import random +import pandas as pd +import numpy as np +from scipy import stats, linalg +import seaborn as sns +from sklearn import neighbors +from sklearn.decomposition import PCA as pca +from sklearn.preprocessing import StandardScaler as stscale +from sklearn.impute import SimpleImputer +from sklearn.neural_network import MLPClassifier +from sklearn.ensemble import RandomForestClassifier +import matplotlib.pyplot as plt +from matplotlib.pyplot import cm +from mpl_toolkits.mplot3d import Axes3D + + +class Bunch: + def __init__(self, **kwds): + self.__dict__.update(kwds) + + +def partial_corr(mtrx): + """Calculates linear partial correlation coefficients + + Returns the sample linear partial correlation coefficients between pairs of + variables in mtrx, controlling for the remaining variables in mtrx. + + + + Parameters + ---------- + mtrx : array-like, shape (n, p) + Array with the different variables. Each column of mtrx is taken as a + variable + + + Returns + ------- + P : array-like, shape (p, p) + P[i, j] contains the partial correlation of mtrx[:, i] and mtrx[:, j] + controlling for the remaining variables in mtrx. + + Notes + ----- + + Partial Correlation in Python (clone of Matlab's partialcorr) + + This uses the linear regression approach to compute the partial + correlation (might be slow for a huge number of variables). The + algorithm is detailed here: + + http://en.wikipedia.org/wiki/Partial_correlation#Using_linear_regression + + Taking X and Y two variables of interest and Z the matrix with all the + variable minus {X, Y}, the algorithm can be summarized as + + 1) perform a normal linear least-squares regression with X as the target + and Z as the predictor + 2) calculate the residuals in Step #1 + 3) perform a normal linear least-squares regression with Y as the target and + Z as the predictor + 4) calculate the residuals in Step #3 + 5) calculate the correlation coefficient between the residuals from Steps #2 + and #4 + + The result is the partial correlation between X and Y while controlling for + the effect of Z + + Adapted from code by Fabian Pedregosa-Izquierdo: + Date: Nov 2014 + Author: Fabian Pedregosa-Izquierdo, f@bianp.net + Testing: Valentina Borghesani, valentinaborghesani@gmail.com + + """ + + mtrx = np.asarray(mtrx) + pfeat = mtrx.shape[1] + pcorr = np.zeros((pfeat, pfeat), dtype=np.float) + for i in range(pfeat): + pcorr[i, i] = 1 + for j in range(i+1, pfeat): + idx = np.ones(pfeat, dtype=np.bool) + idx[i] = False + idx[j] = False + beta_i = linalg.lstsq(mtrx[:, idx], mtrx[:, j])[0] + beta_j = linalg.lstsq(mtrx[:, idx], mtrx[:, i])[0] + + res_j = mtrx[:, j] - mtrx[:, idx].dot(beta_i) + res_i = mtrx[:, i] - mtrx[:, idx].dot(beta_j) + + corr = stats.pearsonr(res_i, res_j)[0] + pcorr[i, j] = corr + pcorr[j, i] = corr + + return pcorr + + +def kmo(dataset): + """Calculates the Kaiser-Meyer-Olkin measure on an input dataset + + Parameters + ---------- + dataset : array-like, shape (n, p) + Array containing n samples and p features. Must have no NaNs. + Ideally scaled before performing test. + + Returns + ------- + kmostat : float + KMO test value + + Notes + ----- + Based on calculations shown here: + + http://www.statisticshowto.com/kaiser-meyer-olkin/ + + -- 0.00-0.49 unacceptable + -- 0.50-0.59 miserable + -- 0.60-0.69 mediocre + -- 0.70-0.79 middling + -- 0.80-0.89 meritorious + -- 0.90-1.00 marvelous + + """ + + # Correlation matrix and the partial covariance matrix. + corrmatrix = np.corrcoef(dataset.transpose()) + pcorr = partial_corr(dataset) + + # Calculation of the KMO statistic + matrix = np.multiply(corrmatrix, corrmatrix) + rows = matrix.shape[0] + cols = matrix.shape[1] + rij = np.sum(matrix) - np.trace(matrix) + uij = np.sum(pcorr) - np.trace(pcorr) + kmostat = rij/(rij+uij) + print(kmostat) + return kmostat + + +def pca_analysis(dataset, dropcols=[], imputenans=True, scale=True, + rem_outliers=True, out_thresh=10, n_components=5, + existing_model=False, model_file='Optional'): + """Performs a primary component analysis on an input dataset + + Parameters + ---------- + dataset : pandas.core.frame.DataFrame, shape (n, p) + Input dataset with n samples and p features + dropcols : list + Columns to exclude from pca analysis. At a minimum, user must exclude + non-numeric columns. + imputenans : bool + If True, impute NaN values as column means. + scale : bool + If True, columns will be scaled to a mean of zero and a standard + deviation of 1. + n_components : int + Desired number of components in principle component analysis. + + Returns + ------- + pcadataset : diff_classifier.pca.Bunch + Contains outputs of PCA analysis, including: + scaled : numpy.ndarray, shape (n, p) + Scaled dataset with n samples and p features + pcavals : pandas.core.frame.DataFrame, shape (n, n_components) + Output array of n_component features of each original sample + final : pandas.core.frame.DataFrame, shape (n, p+n_components) + Output array with principle components append to original array. + prcomps : pandas.core.frame.DataFrame, shape (5, n_components) + Output array displaying the top 5 features contributing to each + principle component. + prvals : dict of list of str + Output dictionary of of the pca scores for the top 5 features + contributing to each principle component. + components : pandas.core.frame.DataFrame, shape (p, n_components) + Raw pca scores. + + """ + pd.options.mode.chained_assignment = None # default='warn' + dataset_num = dataset.drop(dropcols, axis=1) + dataset_num = dataset_num.replace([np.inf, -np.inf], np.nan) + + if rem_outliers: + for i in range(10): + for col in dataset_num.columns: + xmean = np.mean(dataset_num[col]) + xstd = np.std(dataset_num[col]) + + counter = 0 + for x in dataset_num[col]: + if x > xmean + out_thresh*xstd: + dataset[col][counter] = np.nan + dataset_num[col][counter] = np.nan + if x < xmean - out_thresh*xstd: + dataset[col][counter] = np.nan + dataset_num[col][counter] = np.nan + counter = counter + 1 + + dataset_raw = dataset_num.values + + # Fill in NaN values + if imputenans: + imp = SimpleImputer(missing_values=np.nan, strategy='mean') + imp.fit(dataset_raw) + dataset_clean = imp.transform(dataset_raw) + else: + dataset_clean = dataset_raw + + # Scale inputs + if scale: + if existing_model: + scaler = model_file.scaler + dataset_scaled = model_file.scaler.transform(dataset_clean) + else: + scaler = stscale() + scaler.fit(dataset_clean) + dataset_scaled = scaler.transform(dataset_clean) + else: + dataset_scaled = dataset_clean + + pcadataset = Bunch(scaled=dataset_scaled) + + if existing_model: + pca1 = model_file.pcamodel + else: + pca1 = pca(n_components=n_components) + pca1.fit(dataset_scaled) + + if not existing_model: + # Cumulative explained variance ratio + cum_var = 0 + explained_v = pca1.explained_variance_ratio_ + print('Cumulative explained variance:') + for i in range(0, n_components): + cum_var = cum_var + explained_v[i] + print('{} component: {}'.format(i, cum_var)) + + prim_comps = {} + pcadataset.prvals = {} + comps = pca1.components_ + pcadataset.components = pd.DataFrame(comps.transpose()) + for num in range(0, n_components): + highest = np.abs(pcadataset.components[ + num]).values.argsort()[-5:][::-1] + pels = [] + pcadataset.prvals[num] = pcadataset.components[num].values[highest] + for col in highest: + pels.append(dataset_num.columns[col]) + prim_comps[num] = pels + + # Main contributors to each primary component + pcadataset.prcomps = pd.DataFrame.from_dict(prim_comps) + pcadataset.pcavals = pd.DataFrame(pca1.transform(dataset_scaled)) + pcadataset.final = pd.concat([dataset, pcadataset.pcavals], axis=1) + pcadataset.pcamodel = pca1 + pcadataset.scaler = scaler + + return pcadataset + + +def recycle_pcamodel(pcamodel, df, imputenans=True, scale=True): + if imputenans: + imp = SimpleImputer(missing_values='NaN', strategy='mean', axis=0) + imp.fit(df) + df_clean = imp.transform(df) + else: + df_clean = df + + # Scale inputs + if scale: + scaler = stscale() + scaler.fit(df_clean) + df_scaled = scaler.transform(df_clean) + else: + df_scaled = df_clean + + pcamodel.fit(df_scaled) + pcavals = pd.DataFrame(pcamodel.transform(df_scaled)) + pcafinal = pd.concat([df, pcavals], axis=1) + + return pcafinal + + +def plot_pca(datasets, figsize=(8, 8), lwidth=8.0, + labels=['Sample1', 'Sample2'], savefig=True, filename='test.png', + rticks=np.linspace(-2, 2, 5), dpi=300, labelsize=20): + """Plots the average output features from a PCA analysis in polar + coordinates + + Parameters + ---------- + datasets : dict of numpy.ndarray + Dictionary with n samples and p features to plot. + figize : list + Dimensions of output figure e.g. (8, 8) + lwidth : float + Width of plotted lines in figure + labels : list of str + Labels to display in legend. + savefig : bool + If True, saves figure + filename : str + Desired output filename + + """ + + fig = plt.figure(figsize=figsize) + for key in datasets: + N = datasets[key].shape[0] + width = (2*np.pi) / N + color = iter(cm.viridis(np.linspace(0, 0.9, len(datasets)))) + + theta = np.linspace(0.0, 2 * np.pi, N+1, endpoint=True) + radii = {} + bars = {} + + ax = plt.subplot(111, polar=True) + counter = 0 + for key in datasets: + c = next(color) + radii[key] = np.append(datasets[key], datasets[key][0]) + bars[key] = ax.plot(theta, radii[key], linewidth=lwidth, color=c, + label=labels[counter]) + counter = counter + 1 + plt.legend(bbox_to_anchor=(1, 1), loc=2, borderaxespad=0., + frameon=False, fontsize=labelsize+4) + + # # Use custom colors and opacity + # for r, bar in zip(radii, bars): + # bar.set_facecolor(plt.cm.jet(np.abs(r / 2.5))) + # bar.set_alpha(0.8) + ax.set_xticks(np.pi/180. * np.linspace(0, 360, N, endpoint=False)) + ax.set_xticklabels(list(range(0, N)), fontsize=labelsize) + ax.set_ylim([min(rticks), max(rticks)+1]) + ax.set_yticks(rticks) + ax.yaxis.set_tick_params(labelsize=labelsize) + + if savefig: + plt.savefig(filename, bbox_inches='tight', dpi=dpi) + + plt.show() + + +def build_model(rawdata, feature, featvals, equal_sampling=True, + tsize=20, from_end=True, input_cols=6, model='KNN', + **kwargs): + """Builds a K-nearest neighbor model using an input dataset. + + Parameters + ---------- + rawdata : pandas.core.frames.DataFrame + Raw dataset of n samples and p features. + feature : string or int + Feature in rawdata containing output values on which KNN + model is to be based. + featvals : string or int + All values that feature can take. + equal_sampling : bool + If True, training dataset will contain an equal number + of samples that take each value of featvals. If false, + each sample in training dataset will be taken randomly + from rawdata. + tsize : int + Size of training dataset. If equal_sampling is False, + training dataset will be exactly this size. If True, + training dataset will contain N x tsize where N is the + number of unique values in featvals. + n_neighbors : int + Number of nearest neighbors to be used in KNN + algorithm. + from_end : int + If True, in_cols will select features to be used as + training data defined end of rawdata e.g. + rawdata[:, -6:]. If False, input_cols will be read + as a tuple e.g. rawdata[:, 10:15]. + input_col : int or tuple + Defined in from_end above. + + Returns + ------- + clf : sklearn.neighbors.classification.KNeighborsClassifier + KNN model + X : numpy.ndarray + training input dataset used to create clf + y : numpy.ndarray + training output dataset used to create clf + + """ + + defaults = {'n_neighbors': 5, 'NNsolver': 'lbfgs', 'NNalpha': 1e-5, + 'NNhidden_layer': (5, 2), 'NNrandom_state': 1, + 'n_estimators': 10} + + for defkey in defaults.keys(): + if defkey not in kwargs.keys(): + kwargs[defkey] = defaults[defkey] + + if equal_sampling: + for featval in featvals: + if from_end: + test = rawdata[rawdata[feature] == featval + ].values[:, -input_cols:] + else: + test = rawdata[rawdata[feature] == featval + ].values[:, input_cols[0]:input_cols[1]] + to_plot = np.array(random.sample(range(0, test.shape[0] + ), tsize)) + if featval == featvals[0]: + X = test[to_plot, :] + y = rawdata[rawdata[feature] == featval + ][feature].values[to_plot] + else: + X = np.append(X, test[to_plot, :], axis=0) + y = np.append(y, rawdata[rawdata[feature] == featval + ][feature].values[to_plot], axis=0) + + else: + if from_end: + test = rawdata.values[:, -input_cols:] + else: + test = rawdata.values[:, input_cols[0]:input_cols[1]] + to_plot = np.array(random.sample(range(0, test.shape[0]), tsize)) + X = test[to_plot, :] + y = rawdata[feature].values[to_plot] + + if model is 'KNN': + clf = neighbors.KNeighborsClassifier(kwargs['n_neighbors']) + elif model is 'MLP': + clf = MLPClassifier(solver=kwargs['NNsolver'], alpha=kwargs['NNalpha'], + hidden_layer_sizes=kwargs['NNhidden_layer'], + random_state=kwargs['NNrandom_state']) + else: + clf = RandomForestClassifier(n_estimators=kwargs['n_estimators']) + + clf.fit(X, y) + + return clf, X, y + + +def predict_model(model, X, y): + """Calculates fraction correctly predicted using input KNN + model + + Parameters + ---------- + model : sklearn.neighbors.classification.KNeighborsClassifier + KNN model + X : numpy.ndarray + training input dataset used to create clf + y : numpy.ndarray + training output dataset used to create clf + + Returns + ------- + pcorrect : float + Fraction of correctly predicted outputs using the + input KNN model and the input test dataset X and y + + """ + yp = model.predict(X) + correct = np.zeros(y.shape[0]) + for i in range(0, y.shape[0]): + if y[i] == yp[i]: + correct[i] = 1 + + pcorrect = np.average(correct) + # print(pcorrect) + return pcorrect + + +def feature_violin(df, label='label', lvals=['yes', 'no'], fsubset=3, **kwargs): + """Creates violinplot of input feature dataset + + Designed to plot PCA components from pca_analysis. + + Parameters + ---------- + df : pandas.core.frames.DataFrame + Must contain a group name column, and numerical feature columns. + label : string or int + Name of group column. + lvals : list of string or int + All values that group column can take + fsubset : int or list of int + Features to be plotted. If integer, will plot range(fsubset). + If list, will only plot features contained in fsubset. + **kwargs : variable + figsize : tuple of int or float + Dimensions of output figure + yrange : list of int or float + Range of y axis + xlabel : string + Label of x axis + labelsize : int or float + Font size of x label + ticksize : int or float + Font size of y tick labels + fname : None or string + Name of output file + legendfontsize : int or float + Font size of legend + legendloc : int + Location of legend in plot e.g. 1, 2, 3, 4 + + """ + + defaults = {'figsize': (12, 5), 'yrange': [-20, 20], 'xlabel': 'Feature', + 'labelsize': 20, 'ticksize': 16, 'fname': None, + 'legendfontsize': 12, 'legendloc': 1, 'dpi': 300} + + for defkey in defaults.keys(): + if defkey not in kwargs.keys(): + kwargs[defkey] = defaults[defkey] + + # Restacking input data + groupsize = [] + featcol = [] + valcol = [] + feattype = [] + + if isinstance(fsubset, int): + frange = range(fsubset) + else: + frange = fsubset + + for feat in frange: + groupsize.extend(df[label].values) + featcol.extend([feat]*df[label].values.shape[0]) + valcol.extend(df[feat].values) + + to_violind = {'label': groupsize, 'Feature': featcol, + 'Feature Value': valcol} + to_violin = pd.DataFrame(data=to_violind) + + # Plotting function + fig, ax = plt.subplots(figsize=kwargs['figsize']) + sns.violinplot(x="Feature", y="Feature Value", hue="label", data=to_violin, + palette="Pastel1", hue_order=lvals, + figsize=kwargs['figsize']) + + # kwargs + ax.tick_params(axis='both', which='major', labelsize=kwargs['ticksize']) + plt.xlabel(kwargs['xlabel'], fontsize=kwargs['labelsize']) + plt.ylabel('', fontsize=kwargs['labelsize']) + plt.ylim(kwargs['yrange']) + plt.legend(loc=kwargs['legendloc'], prop={'size': kwargs['legendfontsize']}) + if kwargs['fname'] is None: + plt.show() + else: + plt.savefig(kwargs['fname'], dpi=kwargs['dpi']) + + return to_violin + + +def feature_plot_2D(dataset, label, features=[0, 1], lvals=['PEG', 'PS'], + randsel=True, randcount=200, **kwargs): + """Plots two features against each other from feature dataset. + + Parameters + ---------- + dataset : pandas.core.frames.DataFrame + Must comtain a group column and numerical features columns + labels : string or int + Group column name + features : list of int + Names of columns to be plotted + randsel : bool + If True, downsamples from original dataset + randcount : int + Size of downsampled dataset + **kwargs : variable + figsize : tuple of int or float + Size of output figure + dotsize : float or int + Size of plotting markers + alpha : float or int + Transparency factor + xlim : list of float or int + X range of output plot + ylim : list of float or int + Y range of output plot + legendfontsize : float or int + Font size of legend + labelfontsize : float or int + Font size of labels + fname : string + Filename of output figure + + Returns + ------- + xy : list of lists + Coordinates of data on plot + + """ + defaults = {'figsize': (8, 8), 'dotsize': 70, 'alpha': 0.7, 'xlim': None, + 'ylim': None, 'legendfontsize': 12, 'labelfontsize': 20, + 'fname': None, 'legendloc': 2} + + for defkey in defaults.keys(): + if defkey not in kwargs.keys(): + kwargs[defkey] = defaults[defkey] + + tgroups = {} + xy = {} + counter = 0 + labels = dataset[label].unique() + for lval in lvals: + tgroups[counter] = dataset[dataset[label] == lval] + counter = counter + 1 + + N = len(tgroups) + color = iter(cm.viridis(np.linspace(0, 0.9, N))) + + fig = plt.figure(figsize=kwargs['figsize']) + ax1 = fig.add_subplot(111) + counter = 0 + for key in tgroups: + c = next(color) + xy = [] + if randsel: + to_plot = random.sample(range(0, len(tgroups[key][0].tolist())), + randcount) + for key2 in features: + xy.append(list(tgroups[key][key2].tolist()[i] for i in to_plot)) + else: + for key2 in features: + xy.append(tgroups[key][key2]) + ax1 = plt.scatter(xy[0], xy[1], c=c, s=kwargs['dotsize'], + alpha=kwargs['alpha'], label=labels[counter]) + counter = counter + 1 + + if kwargs['xlim'] is not None: + plt.xlim(kwargs['xlim']) + if kwargs['ylim'] is not None: + plt.ylim(kwargs['ylim']) + + plt.legend(fontsize=kwargs['legendfontsize'], frameon=False, + borderaxespad=0., + bbox_to_anchor=(1.05, 1)) + plt.xlabel('Prin. Component {}'.format(features[0]), + fontsize=kwargs['labelfontsize']) + plt.ylabel('Prin. Component {}'.format(features[1]), + fontsize=kwargs['labelfontsize']) + + if kwargs['fname'] is None: + plt.show() + else: + plt.savefig(kwargs['fname']) + + return xy + + +def feature_plot_3D(dataset, label, features=[0, 1, 2], lvals=['PEG', 'PS'], + randsel=True, randcount=200, **kwargs): + """Plots three features against each other from feature dataset. + + Parameters + ---------- + dataset : pandas.core.frames.DataFrame + Must comtain a group column and numerical features columns + labels : string or int + Group column name + features : list of int + Names of columns to be plotted + randsel : bool + If True, downsamples from original dataset + randcount : int + Size of downsampled dataset + **kwargs : variable + figsize : tuple of int or float + Size of output figure + dotsize : float or int + Size of plotting markers + alpha : float or int + Transparency factor + xlim : list of float or int + X range of output plot + ylim : list of float or int + Y range of output plot + zlim : list of float or int + Z range of output plot + legendfontsize : float or int + Font size of legend + labelfontsize : float or int + Font size of labels + fname : string + Filename of output figure + + Returns + ------- + xy : list of lists + Coordinates of data on plot + + """ + + defaults = {'figsize': (8, 8), 'dotsize': 70, 'alpha': 0.7, 'xlim': None, + 'ylim': None, 'zlim': None, 'legendfontsize': 12, + 'labelfontsize': 10, 'fname': None, 'dpi': 300, + 'noticks': True, 'ticksize': 10} + + for defkey in defaults.keys(): + if defkey not in kwargs.keys(): + kwargs[defkey] = defaults[defkey] + + axes = {} + fig = plt.figure(figsize=(14, 14)) + axes[1] = fig.add_subplot(221, projection='3d') + axes[2] = fig.add_subplot(222, projection='3d') + axes[3] = fig.add_subplot(223, projection='3d') + axes[4] = fig.add_subplot(224, projection='3d') + color = iter(cm.viridis(np.linspace(0, 0.9, 3))) + angle1 = [60, 0, 0, 0] + angle2 = [240, 240, 10, 190] + + tgroups = {} + xy = {} + counter = 0 + #labels = dataset[label].unique() + for lval in lvals: + tgroups[counter] = dataset[dataset[label] == lval] + #print(lval) + #print(tgroups[counter].shape) + counter = counter + 1 + + N = len(tgroups) + color = iter(cm.viridis(np.linspace(0, 0.9, N))) + + counter = 0 + for key in tgroups: + c = next(color) + xy = [] + if randsel: + #print(range(0, len(tgroups[key][0].tolist()))) + to_plot = random.sample(range(0, len(tgroups[key][0].tolist())), + randcount) + for key2 in features: + xy.append(list(tgroups[key][key2].tolist()[i] for i in to_plot)) + else: + for key2 in features: + xy.append(tgroups[key][key2]) + + acount = 0 + for ax in axes: + axes[ax].scatter(xy[0], xy[1], xy[2], c=c, s=kwargs['dotsize'], alpha=kwargs['alpha'])#, label=labels[counter]) + if kwargs['xlim'] is not None: + axes[ax].set_xlim3d(kwargs['xlim'][0], kwargs['xlim'][1]) + if kwargs['ylim'] is not None: + axes[ax].set_ylim3d(kwargs['ylim'][0], kwargs['ylim'][1]) + if kwargs['zlim'] is not None: + axes[ax].set_zlim3d(kwargs['zlim'][0], kwargs['zlim'][1]) + axes[ax].view_init(angle1[acount], angle2[acount]) + axes[ax].set_xlabel('{}'.format(features[0]), + fontsize=kwargs['labelfontsize']) + axes[ax].set_ylabel('{}'.format(features[1]), + fontsize=kwargs['labelfontsize']) + axes[ax].set_zlabel('{}'.format(features[2]), + fontsize=kwargs['labelfontsize']) + if kwargs['noticks']: + axes[ax].set_xticklabels('') + axes[ax].set_yticklabels('') + axes[ax].set_zticklabels('') + else: + axes[ax].xaxis.set_tick_params(labelsize=kwargs['ticksize']) + axes[ax].yaxis.set_tick_params(labelsize=kwargs['ticksize']) + axes[ax].zaxis.set_tick_params(labelsize=kwargs['ticksize']) + acount = acount + 1 + counter = counter + 1 + + # plt.legend(fontsize=kwargs['legendfontsize'], frameon=False) + axes[3].set_xticks([]) + axes[4].set_xticks([]) + + if kwargs['fname'] is None: + plt.show() + else: + plt.savefig(kwargs['fname'], dpi=kwargs['dpi']) diff --git a/diff_classifier/scripting_instructions.md b/diff_classifier/scripting_instructions.md new file mode 100644 index 0000000..0127873 --- /dev/null +++ b/diff_classifier/scripting_instructions.md @@ -0,0 +1,59 @@ +# Introduction + +When implementing the Fiji package with Jython, I needed to figure out how option in the GUI interface in Fiji +corresponded to the variables and actions in the python script. This is a working attempt at doing so. It +appears that different steps in the GUI are organized into separate packages corresponding to folders in the +trackmate package available on GitHub (https://github.com/fiji/TrackMate/releases/tag/TrackMate_-3.5.3). + +# Detection +First, the user must import the desired detection method, referred to as a Factory in the code. I identified +the following options: + +- BlockLogDetectorFactory +- DogDetectorFactory +- DownsampleLogDetectorFactory +- LogDetectorFactory +- ManualDetectorFactory +- SpotDetectorFactory + +Note that all these options are selected in the GUI interface when prepping spot detection. Downstream +steps in detection will depend on the method selected, so I will continue with the LogDetector method, +since the example script uses this method. + +Examining the LogDetectorFactory java file, it appears that the different user inputs required for analysis +are contained in the Detector Keys objects. The following objects must be defined: + +- KEY__DO_MEDIAN_FILTERING +- KEY_DO_SUBPIXEL_LOCALIZATION +- KEY_RADIUS +- KEY_TARGET_CHANNEL +- KEY_THRESHOLD + +# Feature Filter +This was the one I wanted to be sure to get into the nitty-gritty, because the example script only includes +a single feature filter, quality. What if you want to include multiple features? These features are +included in individual Factory files in the features/spot folder. + +**Intensity Analyzer Factory** +- MEAN_INTENSITY +- MEDIAN_INTENSITY +- MIN_INTENSITY +- MAX_INTENSITY +- TOTAL_INTENSITY +- STANDARD_DEVIATION + +**Morphology Analyzer Factory** +- ELLIPSOIDFIT_SEMIAXISLENGTH_C +- ELLIPSOIDFIT_SEMIAXISLENGTH_B +- ELLIPSOIDFIT_SEMIAXISLENGTH_A +- ELLIPSOIDFIT_AXISPHI_C +- ELLIPSOIDFIT_AXISPHI_B +- ELLIPSOIDFIT_AXISPHI_A +- ELLIPSOIDFIT_AXISTHETA_C +- ELLIPSOIDFIT_AXISTHETA_B +- ELLIPSOIDFIT_AXISTHETA_A + +**Spot Radius Estimator Factory** +- ESTIMATED_DIAMETER + +# Tracking diff --git a/diff_classifier/utils.py b/diff_classifier/utils.py new file mode 100644 index 0000000..994e59e --- /dev/null +++ b/diff_classifier/utils.py @@ -0,0 +1,68 @@ +"""Utility functions used throughout diff_classifier. + +This module includes general functions for tasks such as importing files and +converting between data types. Currently only includes a function to generate +pandas dataframes for csv output from Trackmate. + +""" +import pandas as pd + + +def csv_to_pd(csvfname): + """Reads Trackmate csv output file and converts to pandas dataframe. + + A specialized function designed specifically for TrackMate output files. + This edits out the header at the beginning of the file. + + Parameters + ---------- + csvfname : string + Output csv from a file similar to trackmate_template. Must + include line 'Data starts here.\n' line in order to parse correctly. + + Returns + ------- + data : pandas DataFrame + Contains all trajectories from csvfname. + + Examples + -------- + >>> data = csv_to_pd('../data/test.csv') + + """ + csvfile = open(csvfname) + + try: + line = 'test' + counter = 0 + while line != 'Data starts here.\n': + line = csvfile.readline() + counter = counter + 1 + if counter > 2000: + break + + data = pd.read_csv(csvfname, skiprows=counter) + data.sort_values(['Track_ID', 'Frame'], ascending=[1, 1]) + data = data.astype('float64') + + partids = data.Track_ID.unique() + counter = 0 + for partid in partids: + data.loc[data.Track_ID == partid, 'Track_ID'] = counter + counter = counter + 1 + except: + print('No data in csv file.') + rawd = {'Track_ID': [], + 'Spot_ID': [], + 'Frame': [], + 'X': [], + 'Y': [], + 'Quality': [], + 'SN_Ratio': [], + 'Mean_Intensity': []} + cols = ['Track_ID', 'Spot_ID', 'Frame', 'X', 'Y', 'Quality', 'SN_Ratio', 'Mean_Intensity'] + data = pd.DataFrame(data=rawd, index=[]) + data = data[cols] + data = data.astype('float64') + + return data diff --git a/diff_classifier/version.py b/diff_classifier/version.py new file mode 100644 index 0000000..62d1fd8 --- /dev/null +++ b/diff_classifier/version.py @@ -0,0 +1,54 @@ +from __future__ import absolute_import, division, print_function +import os.path as op +import os + +# Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" +_version_major = 0 +_version_minor = 1 +_version_micro = 0 # use '' for first of series, number for 1 and above +_version_extra = 'dev' +# _version_extra = '' # Uncomment this for full releases + +# Construct full version string from these. +_ver = [_version_major, _version_minor] +if _version_micro: + _ver.append(_version_micro) +if _version_extra: + _ver.append(_version_extra) + +__version__ = '.'.join(map(str, _ver)) + +CLASSIFIERS = ["Development Status :: 3 - Alpha", + "Environment :: Console", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python"] + +# Description should be a one-liner: +description = " " + +# Long description will go up on the pypi page +long_description = """ """ + +NAME = "diff_classifier" +MAINTAINER = "Nels Schimek" +MAINTAINER_EMAIL = "nlsschim@uw.edu" +DESCRIPTION = description +LONG_DESCRIPTION = long_description +LONG_DESCRIPTION_CONTENT_TYPE = 'text/markdown' +URL = "https://github.com/ccurtis7/diff_classifier" +DOWNLOAD_URL = "" +LICENSE = "MIT" +AUTHOR = "Chad Curtis" +AUTHOR_EMAIL = "ccurtis7@uw.edu" +PLATFORMS = "OS Independent" +MAJOR = _version_major +MINOR = _version_minor +MICRO = _version_micro +VERSION = __version__ +PACKAGE_DATA = {'diff_classifier': [op.join('data', '*')]} + +src_dir = op.dirname(op.abspath(__file__)) +requires_path = op.abspath(op.join(src_dir, "requirements.txt")) +with open(requires_path) as f: + REQUIRES = [line.strip('\n') for line in f.readlines()] diff --git a/dist/diff_classifier-0.1.dev0-py3-none-any.whl b/dist/diff_classifier-0.1.dev0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..0024d4e743f52a4df8ecf278abe4cc9560063b49 GIT binary patch literal 53692 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 B3Dp1q literal 0 HcmV?d00001 diff --git a/dist/diff_classifier-0.1.dev0-py3.7.egg b/dist/diff_classifier-0.1.dev0-py3.7.egg new file mode 100644 index 0000000000000000000000000000000000000000..4237c084e986593294f44dfd6012a6a390e87d46 GIT binary patch literal 106967 zcmZs?1CS^|vn4#XZQHhO+qP{RcWm2tY}>YN+cSH=-H83)+y8Y$b#!#}iOM>e*;SFJ zNHqWKnlNcK$bcaF z`VsXGxGC95VX@3Y$r6vw;i9c#Z4-SK%Hnp>Tbn&XsOdr&c(TK+Gi{R)fwG?`ogT{HWI<$0PwO8W2VOvX&vcvBsx!F)p^_H zR9aq17av7Gy!Lqc_q4ER1r%;lO+rIYW>qUpjAGhQNWathPlK^`0ZmD8yI%tjH-MdT|bbke9&b&47g9E6}7@q7fqu{Z4Ef;D@AU5 zC0r?_CDA>cT*Bbcml}_|o=rVabNU?0`|sWcAHNRD;d5{6JU#vhR~cjl=_EOb!PML z>!^S2zoDE=9bGM*O#gRZw9(RV0}L>ruRYL+y>#ACa(g7Dtg?sYZ24Pw=xaQdxn_x7 z7K4!^_gG;LVNb_OC|c}Fo4?8&LcV`%g2`TNHJC5ppf>~6X9d%Q+EgQqoB(s`70-LA zq=9C4vdNidA&S~OKe^rg9FCyy|NEeza;rCy{+WjGuc7^G|2HmN>>c!NOx;Xv{=xr3TbdOZguV;9p?tKAqDhYM=2MYS+8F1G%H8=7m>pW3)iN>{lBQdy*qEy#jVfiNOToO5 z=|hW%N5W8Khsz#JHGGrs(v9;97;7N#I#b5zCpbo#`M2Jevk+aM7+yugm zP(lKj)5wGLDkZ$b=rXbjEvLq$ImkN83JV(mWi;oC++<>Uqzsi^B7yhqXFRJeSE}Uq=15(;jH0nbK&83C` z&SbIi2K*tfR6hq+#mZ*6q_L8uq;*rUFNcg(T$HgsX_I7c!wO)v2)#LM&gzD`Gje5p zLT4#M4@Mrt^i=0@5^XlU^t)MiI4|g-;xmf@E3U+jf_gm z_AZE2hdS^V&M8rv!$|~9d1;SJpe|YNZIB)-Cl=X>ON%G?_GZzLs`4@e>x>nu?A*lz zA2ySBD;)A+8${@IGS-{oC_&5FWyc!x9L9sz8{2x*kZaenHKMNDOkVRV&w*$Lgx4O{ zoyBJNk%+vrj+(PhIPRM`-@NZy)`rC=KE=y&XL22d_i~p;2N{o?_c!N_JPT>Q{j`F? z;FGat-s)HJ`KH*_PneIEgW7!_h*3FzoT*XV)A14}G0Qu_5Xo!=;7D|%z5Ez+-d_jm zYhvgK<(nR-Z_@)*9qA++I$P z^^r5LObqh=oM(Un%pFntAqyd0+28gr0CJeeyUyrOf4Xm91W4((^PY&DAE3Lz@LkCR zk00D}bCHbL>rx&v$VLzrTFpCrpz6o-$Vpp=Q8b~K|MDCBPs$BUVY7_?OSv2H|2gGM zTuuL_T#c%9+$I}B&!f6bH-t@DOBZY+9>`U}`ZkcZ4Zg%eA$uUwC>pjTip0du_v?39 zBEEL9C>*Md)ez>)VHS98gtl&Iwo9=Fv!nT#EpmG^Y!&rESvHV36ysJ`NmB?g+8@p~ z^@q>AA1k&j%4bG6(*{}XQ^`fUIX(A;b%i-?vdxznamNK19ZxnkrQ;5SZv{~hp_iZ{ zoBSEk1#eqyUAAn>=ka-jeDc5L_AaC->vUXRZoy?coxN>HDPhvEoD361s%+E;Hfe#I z{7G<9h`E*E9Ksof){B=K_J!6tm{>b9^liYUO1*6mzO4CS<10586qV9{61vysz0F}= zJDRuUMC1%3E|B?`Hrjwil0}y~ECZ%Ak?xnbViDbpC6Fe@*K6EqP2>52Ae}_rXa1gb z)AHZvjR7z#;u_;WOq-b-$F5PW+R2I0l{YvZmo5V$+VE}tI=nvmu4E>2`YqV*Is}OK zdUZDH(w$fq@3Y<&>BVIvJ8_?0q3wAIf|Kv)_`ll49(xyd2HKChVDC5|dc6qpZbWtV zNxpu@xnHtZL@33J*5e-RwNv~lxL|5Ci_nSLrDk%o4|c_+>MsIu$gd*mNui43T?!sL z$IjF@UtRBPSE{sffq0s;9J_u$TBs4msyzD|NKxXO@~?b7q-gJ9LnqWKxYduvsY;68<6 ziE^T9=DbKQ6UVtQ=~rQUSf%h^TL6ZV!bv55li1O>Li5eJgi)WlgeZ%S1mOa@3l;L@ zA(q626#q89)o+r~B|ot*cih-3iLExzpD92MJ>5YUjk+0lF%foaER^u1g;KEKr_8Xo z?9q@K(I+EF89SIn@I`$gzM=X!{o0BNqJwrc#(?Q787ptZ%9SARoZaMb8O^8F!NavLimYYHrJ3JN0pctp~qK91q<{}^C zU~McpD2w`(cs&pO(_cv9^`?zly{(SjOUOOkfy-!S2cZ}+NC7ie$wR)O4qh%nK8C-sm>S{R>|yl z{{HZuQ8QkorH)9K{(^Bib|BP~ewDVFDN#{VP3GVM=uk0H(%-@!bd4abO5%9Qn81KU zH&8>HMFf4-u5&mgdr|vJO!qJR%8=JrHc1`!%HjJAn^7xs`v>;_H{t&$`8DbMl|V9(A)p(#*O!+Uw^zRVtMx|pO&`57QN8*O zgGY`m`^Gjo4c!rc=@VDQn>D7$OWO<;8%;Fp)RQb*TQm9tE*mU78ln0XEZn zLS2G^K}-En)dquMr|}C_r>AeO9?73Rw)GdX>2-O4Ut2VF)eadcw{Bzpny7}#r0j3f zp7EpwL;Iu<+C(n7#;D=v`O3fS%kS%&Q6@hpN0Po^EI$Mm#Q}~kPydW7Dst(joXD&> z^dilx>e!mGPHB#O6onC?ZqXcT^6A{UVxZh*NzA61KD+{R_IvBdX#B(Iii@8BQ{@R| zIb15YdQK0C9RFzhPMATvxM5(OgzB2A$DM`ChlrgqpiyS?%GwHy}lY7@IlnX?SZW-piztNY0xk`B!%kEUR>_!uots8Ot^hPU_+b8 zGbu}!Ztz2`0%1c;frFrBf>i>!r*3^>NC$#Ve$@VMr>tTF0gdh*Sv!xP zyALaYfJJ+7fQBceH@h$foNv<7n~|Yll#)2#0=|W$LJcQKk1%@(pT+rty}D$EMq=_b z2s#Oofv~a^dR!S1c8j2en>&3$Cs~HO?ANyOJ$>feS}2|_>O2it2KK_ZI5Lr{ozjOV zws~C5VjduQjk~OnsvckJ%;st%JLB4IYD6N3Jie6Fb20c&u}DG8`YUJpo0M7zMM3R- z{?#w4l!qOKQx#2(!Ksk=CcrJphc5T2(8<*X=hD)tl={Yc7vL-`sfF-Z3+L+eCYYiT zzdCK}sF*U}sn{e7;qQElxAL9w#+)X_9HnGRtAk|fq5wiJ$qZYSM;Kk#wRlg8UH5?i z8<#`smSa+S&`XdwurYNum^Km54$24sTAgxG(v+J9{oN{SpADJ{=gg5|cnDH($32E&2C5@33@Y%L zYBu^M2X#wIBE=A=DHI>rAwm2<@3WK0JD_Za0M%@i$Es!5PIx#v9caJrfl>&1G^C@C zKMYOCqe-g4%gM!~?u;@V5-4lXFa+$6wgd@b7||?xeki=4u#S^O*Y?{(8Tz`T*RpFa zEP;7>-XDa)J(l?o7T5rUWFu|&Z}#JRrJ0&jdx8sS@A8r z971QxLtp-W(~r2)9eobo2MM|9+~L3oyI~MI7uUlG>t}c<91G_0&nFi5TDzaCN3=+1 zG-W11mP6{GXOierkg-74XS>}Pa5)8YVcVRFhF7S=s@ABa z$hvQEqk>W;vy&Q2yGfBcQ4=fn=HYG6BG1a~UJu&UAm2?fRm3Vuv((cb^&b#K=0H7G zFhBtVE;6m3Mu+=E3|zZ>V}&s?2*9AvKID}XFn!?IH6D^cGh#F$>)M9lrJ9m$E z45%)_#{jBMc!lSM`y(e@2_@D6LQKgdr$~a_|03{79HxKeNXKM;8!;bE-^UzO*QH1P z)DVC*G`FBN#0%s~!q}U(wh01K7h#zG>0pJ=VjrP3(#f||etkoF`kH9%?4G`p^lKyJ zmkewF`VdA4kDI1W>@qNiEAQjZ^hd}24F3CQ3HyP^O6U^l>T?X&^TvUtWxc|$9qTEW z5&3Ey-M_^RWEJ}@5Fb`|7T??XB?MS07 zpw~&daQdb(m~<{AiViGbq1b6>Ay18Pz%~MCM>5Y5|2sOa$bVik581U+wM&wq!+<`0 zlO(7fP$HO&6PM7zLCn+(-8oO+b~RPg-7TtuBeA99ln!PM5GL!ZKO>=#gAuCZhT`l1 z5mfgI)n#5+VM}#ExN@m-v8at_%r(v?c{7^14*o5EYQE|Ft{Ir#LR^Hr0|m8F6}A?S zr-;O{mh|B{C3{2*#hR|7txI#%U1iVJ6!H(46Pd~1z&6({@Inexs!J!xkXo0%g2lj8 zsb2!b{2AB~_@0CU1`(Pl(1dayI)4)cB}92f zowJC@<$sLK-Z)_JWxwuCfCzKo|Nc_md@M1&WIG;->M>x-jSP;#iw~ADoUOtJf9OE5 z^cRe2u5mQUC^I{75MpV3I5R^ajOX5cm^hYEmDa&ng%iWVM)WIqglXw7Lk9zpP3~!V z$kNEt8K`u!aGpvbycU?CdEIRs&Q&$quq@@;~SVm6k2c?7P zaj6$Wgxj+l-~|R3$U5)?m|ECO@9Z+zTDxb{W52RAcT4tvqurYMShi~q|Xw5jm~}w zJcbN1^sQs_XNvTj9Q`q!S%W1gbPN}O7Rn@mLXE-}?BK@%{}}=$VP{HnySr8HdORNC z<`H4=nNHsC+vVoGCzKIH;=l*~&8s*%HEY;Mw~)BaBTP5UEW1Maly)@sEJK^;5M|(- zHhw2he$pXjEy8Lf8p8V}-T^|q3kdER5oL@4yovM_VHtqHL%%{KpRs3h8bmPiSyD#v@(lmi7;3c=L7yMvLvnAEZgZuu<> zs-c&3*D+^->tYlUAcjXV;fWFJvI}H>8bMZ|m;=~_CqqUI_ka#HW6Y|~E*$Fp1TYVIECZa0s<+$^kCHy^0a zEga-bX?axfd~dw0ZsXG)Mb)@s<{NLUQWyQ>!3@bK>|oyLo!Ro~I=D8xaEffvOG2sv zR3&=Ec~(Wg+MfDz{7y-dt$*Imm7EYea&$4VpOu7i^_`zaC*p~Rrm#NKcSQE_ z7kv9t>^1Xj*)(dP{V01a=qTkcOB~E=72SnXTLX}1?F3H-dhBU3&KY#OJ!DwCWL5>b zIu6|Wp;=ZLzkQOJU^e#LEs~HH3|rZhIXU^%0eOCQ>(eNgTpajbqo3p&BzACm7vPU@ zn+YBP(BM8IMHrcB5fS?jHc!KaX;=BtLzNbkUc#-ll&OP-U>o@9Qelj&7Z{d@foOZ} z)G66d;KQc|SfhWjH54%v;+B#xx~Q$;wXF}JHh^AFd7|4ZBZL9jH1(i)S!J1^HJe0VCEfc3znYh0wUo$fJR&Qb-66sQmek6{M1G&xz z!OXiLYmxyu0Bg+vZ+lA5nbMMLYAE@DicN)PBNmG+^93*ISU}(Pr8X_)h3i6hSP(+U zaD;EV5E|2bA^FiIU_qR+Lz~NN%R3(T(`or7f~7vT!NGxAGMfSn63%9?bY+Aj#0t%$ zg41-71dxYoFGAD)7pW)+80c!h4=X()u^F7@mTuU$R4+gywMO@})B=6JPZX_;ip#|# zW8n847y@M2Q7f_^gyZ)|ao}uPD@R91OWv79?<&z)9fz!8>xeO_-3}P-3H(r9l0#|9 zTH@Yfhh>_7W{G@f9R)iQE@O54k>I)sbE=)l>AHj`J&G9a91PDbFK=skJm^B@uc9uz zy7A%+H=-$Jt!WEqnGt=B^g5Q->C?-E<$xvmptyBOa};(oq2aFIlVhq&9y53JOE+-3 zG`aUjDx}7V{OqFX_%djm%0Csqcqg`Ud6QZ;2WdWSBV8hRO7N6)}?s`}D z^+`%Zpq~58y-@T}Y9%QH_{0Pp@yt8*6G6GQJ-cI>^SMWVk^&)Gn+JES#JxK=0*BDA zyyMS!HtFK?nw-`qO)%}5uY9ID$>;LbL{b^>6heCEZ8y7pPi8kcYVSJ|qH3bF5gvU+ z!OE%^IYnGrExVP5yx<@f13OXZC(9kArKjPcN3C|D**2M`lU#+Q#ZR&UZem0E75pQZ z7qpTC(9=*z75a>WZR%0A!}J#C&Gz0&?VKg=>`+9bSN$e3IQQetze1V@^#B=@$s>BD zY^E8cinR8psWs+!mzxeq=>py-)Ds^F9|bH?&2`!kdWw3mn%bs8a?0(KK!O)>tL&k> zqctd(yLRmP7EI@f*%^8kw-axl2?-^RC zs5=F#NTa*lxf8Lhe60?Ikh^0~y85sWt+RSW@YuT;v7#=P=mOYIFKI2-6l}91N1bJQ zSJIE3JzIsL^{x*+eQjaSku|UIIFhf3LR%B8{KRODdkEmOcjd*|!FBk3>#)Ah zD?*8>ekln0m`hSlj2nG*v&~RBKzq2Zd#TI%TG4zGLge@GDGK^bp2}&Poi6lpH3|W- zA1%kQF+NTm^q=(6M1h+c850bdKAaEn1D-Jxy2cI|VVqgyF2cZzlVTMfIa)%WFQfdVT3T3bG7A5|M1RnJl;6PZ@KYYtU z*E!?7lFbj4Lf{EKK2g$19&OOuBE+2Z%pd#S}bW za4RJC9u70CSTlH(JsrXQ{K+!k{h7t5%}qHdWby~f@13M=>RNxjFnfT@0lGdR3)Tn% zi&H<#_ezSFt9kojh~mNjf&Y%Y{G10q*a}uu?a^Hnve!SF!)7d}h`Q~) zg~i50aKa(uW6(g&s-;*9RQIGeO}sQXyJy|+^`<5|z}GCSjqYOq;!=1Ljz(1#HD?x; z%m#!mO(_+@SN3Jb0`Sld7)yf8j~UruT=lcZx&!yWXt%T12~dY*OXY*D9XZI%`jTZD z#heH|Q~On9p7x+aGJhj|SGR<`x~GGDXxA-yRJ~-gQ?Aa8-z9ny{^H;QCq@f8*G^cm z#ZjN&#AY^6129n31vh_~$KONa!{YEXPtFha;0qrQeMGIrW&x8c@l{4^^@MWLM|aUA zlcOv{b^eVWvGwxPVn^$TtgB6@KBHMk)c>3t3m3dtBv~hSR^G50<3!rC9uSn%9mS3d zQ^eSF)pA57kf^*v`c`_`E~GvN@SUYjqi^uugR z%OGYE+YI@9QBAzuJ8;(Jnjqi}lpktG_Ng9w8UMaP{i-*yH+|F!ogA=4(y!98zx3dM zgpPyt2HDk^A+IarPt_Xv;>b$papn(`*1BWn3YA*)hz;|Ab_@k>#3Dh50Mv!xZq!An zWjrSDVRkFOiM3AfhruJF5$1O2?_9?Z;-)jIKY@8cw&~+l5YZ5BG2_QV+E4F6;<5X$ zF=>6m>#FK`yyiLyUsBsLJ|4wO%4nXE?tB9A~ z7I4Vnlu-?KG(9gCbVK4&oTzHa>fW^DMgxiq?#s6<+`|axgsf6wQJLvJ&0*-!7ZRaP z164~$>LyQ(Y-H2-FJK((6fOLx?pTmZp?;hw6bgrp6Wdw3=#7(mQ-&ls9& z93O=#c%T)Q#KD#VtmEmGbXUlVTH4H+ggPz&`l<2xI(sf~XM4uX$H#OZ+smM(Qn))0 zgEWcYSr^{g$qx8t`q;t|1y+ydGLBFv_r^`{VTYfi{ae5;ucQav4I;p^8k$i;P~l-W zl2GP{F^$}$+HY95Z=MN({=!}!7vBufL6jn@2Clw2(b^a6r&*Q}HW}wmbHPSd9?RbK zP>(;QT(*!kc?llCgj(_`5*VCFTBod$B0L&#HVYxR?bhM!I13o_XfYVCXE8Rfr;_2v z7}a$=-8vYk{5gPt{wo2; zRx?9*YOSdTC`XMnYCU@8H0()K(g15}Hi1L;IMPo>c*a|5YL2O zX?Bpwl~RMF>#>TcX7t!7lf|0Mhz>rxD6;xv$R9E}dVZ+g&o-m(sz6{d#tL%-lO%3y zMKo-@Iaet(tg3s=Qn(6h5vABCC5&cjT9IH?zyaGVK&HO$kdF_~MjYJ$0kx6YK5!RT zXKnTd(;Xf{&Skd;g3hpZ9}Jy3b#~VRX?XV_pmfzn5Xm)$%54~lFL&Pe#5vJm#+i&A zbf0wgMF)3A%gpOwBc>X6X}J?Kuq13_pN83V!%O(oO@{ZHpxDTx;0baYKjmkcm~T~g zh)7ugUX4o;?q}_)53&6XLe|OE!R^wiuQ+BT-YY+x8oT}M2|=o#OzU>|^4fO0=-!bu zJQ$yw`;W~^#w)RwziXJt)O&AMbrSmS9u9;pgSKfs^DM--34zrqG$d;NJ>(+EoW`A249Cvrj#-XyF%+2Kd97 z13$$)=kl?>D9Tvhd52MFLG_F$R!w|IqBEJ-ak3#yd4&m36ag)2v>y-h`g z3&Y-DVb2QAj{Pf-=Y{3JbkgX`->*RP@MHz+dg;sZw%gyny}gZAms5{unqqrVrg|Na z9Oo@WPc~6GjaWbAFH68p%&ZgTEPiWxD?6x;;%rSOn|a((sUBL?v_+{+gQ&+kw_0_S zIe*k_=s~Ml@stUvCPN!J8J**hH%+~FsuKklIRHZ7?63qRmZ(m%pAb~;#5@9npV4dC zyq>a?v7B=07xJu9Qh@#47F17aY!LfIU}&lA50*R(%HokZLAJt?ZfX4r8rqFYOcuOa zO|oW}NdK#l4_?FE1;0Fe1$N-J1>xvPTB&%Bj+M7alTiltc#E9LhWe4;*+z1_$3UPz zrso8Chj8xjuCMD>TIf2Ia}?*8^67`ok!A|rfCmg>fXPXcw`40uf7^d$_Rl{2lb<@oghSvDk$ZC!-2UJn!I5IRE#-9$l z6yM_(G z8ggc%V^c@%eIX^dlSDiKlOM-qT6UX zSWwk;B6yi(sWHCMURvBa>_s=(LLww;Da+@gyY?H~TPl|^6Ve-^ha{)t<* zh7SK*+|u?<+H9Hs{pADAbvBdOIk%9M%bt;4%P4CvPO?n$GHOkV!BYiJh!4rMC#A?a zlj-qxN2ezd-O+SBB$vaE^(_n-HmcXS`auIQRYOyqX3|TuR4j9vZZp!%(38+jxNU6c zZ|G_gC7ng42=F)kx59ccT;D@hEqfj=rD7^I(qt>3@}%{gkV)!ARZT!&aAII#t&L>m zUF#@(L;*^}RYEB->^Urv$a0{Ll37wUe($9)(Tht8F->6GB(j%0H@BZM9FauzF^O;t zQ*|_}LTX>HK1ik}Wmi%m>@8#LTOA%2C1ShEAMMx}(^)Yx?NG1sJP=+EQRPYSM)g%b zB5Lb4xNGm~D!#QBg$Z$Oj9e{iSpZNgZ~DZxmuBhWzMU8aMIVl$s~t_Db8xzdj=sZ3gh#*$lQ`l@8?0Ih*m#P z3EEjU3+G^IC_e&I*V+U;;d1&>_c_2@v?S5Z}bPuq?1zcB?R z24W^`rvsj*M}MWRx{jbt{X0iVRS36TehKt^l66u))}IO!i%5mc^ZKq>X@RAY5!){@ zJU7;vsN`AetC{8paCvLN?Fr;;gbo-Usu48um>K-=KznF8L6u=MZR6OI0-nHpAX*b> zUG8m^4mh8uwRp*2!lkOi-Hpn9Y?L{oDlSJ68VHrtQO%w+9;=7^I~NG#OHb_NX47fgwz)u2_>`Op2_;Sm4M@8 z_*vA-^%R0JsBw0CEEk;v z-M1hY;I_?}#xr4;XRCra+6@ow*k--RGIZeC5d$tcm4tF%M39BjZlyl*#;6?Qh`~Is z{=6>AOq4l*lhCec+MJ}{<5TT+?Uy#D1&PN}f#{A6etKk3YK4cNp6QM_NW%ol2%knQ zor#In#h=HX7|oT(MCldnr!bp-UA7j02m1*m!GwCs>W1AP3y=X)`L6q%Z7Q1T@v@*~ zZwL^zmTKoMIt%OTIAl5c+zFGw-3BbddX6T^jD{BzeZ!h2O_+W4n`F=CPzxLOspg37 zb)TEkYWKdIamRV!fxtlBh65B^ucm`ZBS{-aKzTJs?r#h+?w>+d{mx<~z(N-sABMtu zxn|oF6Gub>4qY#({&-F55#eD=#NM88izRpF37JTyq=jn-M~#-~YE=JqbY`)Xm?5lB zz$D4g>(Z$Hkdq%lT03%^Ffp_w#Iv9Zy7b(qIqkO@zl{Y0!BB0S=Gso^)G{i;PxVgl zH?D0rvI^C|CFr9X$*iAA78I$kX&%@RSBtD!Ib#ZH4AwobX@x2Xzg-T&VEUh>fjJ{l z`5NjSlI~GnXvPM#2m8x%cUVFV`f5hRamX!!yPr}bc0LeZEXUTQ3AO&>@YodzD4P}; zvQE{GgXQ{PX?(Z0g>wR2BDVwOPp%j^LDJe4i3gAOQ-sC-LRlZijMR{BGp$1+?RGdV zEoTA~q+;$uVT|iPGyw{ugE@}!#J*ArkZyyLqGqbN1C)9WU`c(*pl(u6+jOT<^7Ji{ z=#p8i`jX`ZyJ-6H;V1Q;xjH}>qEWu_jl!XW0R@(>HG0D#0()3!`6$i0SZi?jQr?6F z5hNObN-14gPib-mWr3D9mKZYYC`KiQ3toaFckNVq6*fwn`ej!G2GnUMRI_0KOp=JK zH2_(^@R~>Pe?^>JXJ@iGAM!RE3M&iChE{MyB=ZCt_soT0y)IKTn8MKAnL@uMK)orJ zF{6T}uH4zAK7eO4U(KI*7}Op?rAN6FA;ZHwz&cHGnDAV%juAfs%bOm+4?&NV-?|qf z@uB#kSj)rm{_3{}FM+ z?^|V|6CJ)|;Ongmne$oX3L$3@-UXw5tU1K|g75IgPf9YmN`NKVrn6SV$N+#e;Rvt7 zC~9zz59ls=V1YH#RdLD);}Q$qMfd=v^fBfgVL4DqMX`l-mfJ~B?*$1Tuc|Ewsa4)5 zo`v`oASQ?o7;v$2QHECSlxfcH9IYo&Q_2kS*adHz+dNLEvp0)7{_Cbp?-ZrI_Jw-? z0ZGe{3rKm5lv$yZpO5V39R!?EOGf(BbAAf)=SC#vGCV@Kk+*>5P%{rDn?(`cnbnpk zURi&iZYbs2qA*9Im^}RnmO8#z7ffMt(P^~E^6ea9EU9IGmXhL12HH1gC0ZpC@tvi$ zkrVUiLp3+z5W5LBYi=pRY2SrST-Cwqhg7 zn2MQ&&}Bs(!*{c?R6BIOyJkRU;5BM`#Ldpc$1r~xo}UY({Dm<+edB7e33Buhen@!0 z0`bueQ-lge&9ujLh>S)Sh6B2~6#|uCpnruOhRmj$?hgV+pPkClvQuXjZ)>wzp$`fo z*qM#=c9p_v%~E5kDm=cvw|w_y3%XE0bx3l8-wmNJsKSN6h-s~oUlX`;BX!8qghQW^Xi&E5r-YtLj)7F%HH2K;SOX3#{;1Ms_pBxp} zK=e4BC5}=b+J&)C=Xz6jr^fX|FcuG}`t>o5pvkgLaR$m*LLbxTWq@VA+EL%%QkAo{ zLCUvpU$s>6^qF<%G2VImsjVvK9<@y1V5+DeSX#UP=NNI% zCai;I-xyV{&{8udI{W>Q4b;c;&oVhrMSJX@Hn1w?6tqIov=%BG2RgIW{{(-}zC%RJ zhQMc)8?J9oYCL}20g9l<9<=9f1kTTo;~Xz9@Jrd8D-<0USf6~e$(d|q%U58D!z$R| zk^uChg=CF=`QGax6W%ttodM+ZZN)l?Z2buB-u(Oz|Hsmuc~#U18Dy++Z~#n)gbt6T z3^ZQDgmuJQdDYoXow(KY-J9f~qmdn(ezhYP*_dv$*dD<&D2hcPfyAYq3}PU6QG_MQ zodHxB_uKnINL{nDF-S;#Z0h);ZXiN#bUYSoZD3@$^G#KM0rj4)Tu{JHvwUrAO2%#O zzXVNETU`WK`xDsee~_)_$vW6#`6|Y>9I~MknfQrTkRYphg)7=L|`8-*PKYN=ER$ZhqmbDU7<06NYLDvD%}=(&J|E9sQ&4$ zVj$w4*8!~;D1zlTRi7H|0Nu_mEW6RAbO+Ty-rkwIv&*z|=!D5_IThWLLu)%hHmF4ei{zvYdm{B6zxq5tGX{O!2EBgPX4lhF+%g$Nto*N#~C+okRt3VM! z^WY65CdQe!gnZ!{@2y>XHp-F4Ji?~Ua53;~Sxtu^^4n$1HSM?OsqA0Itvqgqb+VTT zK}j1g`%v2#Pn&xySX>_rf?u9wZyeF~Uo?>)(SicOufZCUC>uL`$t1&aEHFA<^WN|* zm21?D)aXtZ#3pVAUk~%P*ehh(qlLhAXXEM{@bpPOGpe*Kfk5EK{qd{7z<*sbmA z+Z(wS6)tUBw^N(23n4}#`lp%aSl;)d$JZb3&M#%>0t274--Mp~g_Xv8XMm>b{vN0u zupxKSDeVX*Fb(j0`_J7QUH|qPG^TFCBy|E(jwGogM`NV2_qbu`B82IiV z2(kvDa)0aH-eG2$(XY6ffcR2#^H?@sy+vzBjE-wuuF=RYylX|^9Dn?~VHr@Xg7ni? zbBa}FY&A#c!^))f3)YSO$L)6?3+8>uaXzQv23z3VtH?*YX`utvv3U-==MDH{UPo=Q z(MAo7*o1Q9x1(Ur2`A)@AhOlJ&fRUxe$lx|5OLr>fIXjXrPf=3O8MT!yuzPN%j~7U zkIl83F3-2g%e>bOM1Z#5JEH!va@Dg7mE)dr-l&*mwnbpI6>d8VOn)FO&PDyl$QlXH zK?XGcxaXK|iRb~2m%9gj{&|W@h!E2XI&0SuOuc``vQhg6mC+nnF`Vc;x@+wfR+incqFdhh57qRJtx$-fNNE z6?NC1mSrAbWiX86ezlu4x0P1dILpKkGu1JVc>tfF{n~3s)+Un{O{F|UMy>4zJG^#dbt4r zF#m^BIF`1C=B8Hv+1jt|?R?0V_^Y1>R91sif(uJKO&wXATy@3MAU9oQM&{k_)ZWAZ zsfnRXA{^h6vb1I1>jyv}A({FxC$mlj!GiVuR?rCuA3i8Ant-W@UZ}F%P`*-_tXh*K z6WNhC-aJz~RP$H;0GY4Gv}R~sORoZ7G=o*0D-JwZp4xV(LjoJ4=#j))$#ke;JV^_k z(}$W$QLU0DA$g=;Y>dbC(XcAZat^~whcfqOdd20y$E`kqlfS&AQYDvy+2f2qm}BJi zAZcbf_QRFP?`wD5Kk~J*k`4Ku4eghbkBmB}jB1gyD<-rubjpa)izSB_TYhLx-U|u` zE(Afe!pTT&dzp&;11VL*tbtBKsg22mLm9p`1Pz}2AhuFn)N07)i#J!0|T}Ft$-o`!b#nv0c-FhY;fNof7MiRxsc%|YrRQ7YqEjL;Jc?p z?b4}&`8bz)uTuj)Rka?iLrqmSs)atu2$n0b|C+>+QgC{quTa-`r+N~W?YL)LCke_} z*PwGgv@HJO*eSte0i%1Va$wc6;nbAI9@Z^DANXWW$6QLS{Scl7Bo4ZcD&_jhz-}eD zpFmZ*zD?=bsDl4;KE0Smz-?BYsy%F&ZkXDGu#a!?fNd5kqY|Y!x_g6;3H;egx#)bm z9BL&w+GtgxA!HNPLRD2ncy&#%jH=dAladZH7X1li1A)C^0ZG>E?c!>*ahQK*XGHj( zZkCP2PqT;<5!b11mCOFdB8r`B~BDO%ZeAXgw(+%}tPBQoU(F4Q7prYBYuwK!^tP=9is55vC(H`#7%L)o46+6nOL9 zQyH*sDZdzq)m2-?fYbo797P4LBC@$xgmyRr90dHA5nYk0N;^k>u#)CiHWpKym!prMfJ!UA)@uKuQg$yT@r}hfe7vD}lB0r=u{m8A7GA%n7_4&o~wH+1pdTm1)UBKdyNzm z73&;2I!x`DKFB&l54*&}82ga0fsCYn+ zk~h43n96|r!&$?3qvj~~kZ>5D7oCH4ZQu+077nIOWiB20mBkStu1w(qWUD zHzVdR1yLi$pw2;H1e!kPo4o7+b>NhY!=If$Vx$Kx z@QiD+*wUI44LUiOAupyO4o6_hL*^ETE z^ZT;w0U+dz4t0H!h!0eX+}=1Lp}Wv=+pOWY=T{iYNmwc!-NChiLv#oj(i}ciw)H2z*R7EkYTsj2*nxR5*!=?~0IGm+! z`YhA8ctcnVM-6Qu^aKYVH!pD_CAUwpfsF|*1$-vfJc1(ot4ZpraK-rFMB_=A*o@bx zvT2U2!(tSR2pI6~QPK)w4jpi=rBA`w&MAF8?KhJ%Dc!5u5Su}yD?ZgrR==kwsXu7V zPhiSisTrcW^YiP`wFwStp+cexz!owfYa<(XGZWTDAZ;s|mG zS5#buIz+)b#NMdrpovzEHj=){ z1OL_K(oYE*g}@8WmEzj~bJc4VIZhKZ>)d;Eb%ZY3){b|t*SRs;Y8*MbQCfR{8IIuZ z=TlF(VlMOp58TmOJ<*HG+7)(mh7qM+`ql=o-JYnwc56bA(Yr3Nd_rv_v?8Rh>ryMw z#goTC3(wT9KHeq@_0Twp+}L?L%F$zwcQqwIen8qqom9X%(69VZqx{Q1Zzc@)$I>Ya zG#pJ%Y*0pL9I30F!Jn4c8jNY(cpO=H<)tCSW-cC-VKthurG3nI#k4G;rmvzSqD3S_ zv6LXf`1k&4ZjCOb##WJc#{l85H8*w`@W?KK4OLvEbGpB`1r4C8fkqRdg+!S94iQ}b zkieeKFWSS#g({afr+;P805mS{f6;YLO`0{EBlTD=X`WlE=#=D2=kkCUkZIZXmF)-XQo-SL6tgMJDeKXO0iPUVNEGeTLvA zg00TdG@EVwr8n^-_s$Idd+Ld2C4_${-h&eUxs7g{JyApkd3=%~2&@V`<5 zLH$DXA=T$lRS?6_sHTM}hE;24yf>iM_u{GYniX?nMZl|_Dz5c*gP7ncNJ=3!YEC2m z(j}Q%+XK$0p_qh-cH8!9|4h0!b2vs&wZ(llb1_RKI!YZ4+L3UjM#zGYi+}o!#vvmJ zGTrDa<4}*b+aGtbN|#7F1kpJ0&;u4_%rT6RBS(CQnXrAA7)egDe2Yj1ZVEt<sy7Z>+moyg!4GN#sh>vT#N+b zesu_CDT85E6C>(o5v*k3KFEj$al8@b&QJuy@g7M*q2afIP(679N{K>FobY!9`m6lO(^RSe0_^PlWyR{r5}1QlM2mDD_R`T|sJT0mfw;~Inz z+6vpIWJb-)0qF(URe&@N6+DIAXbGC z;_P|bu;em5+{0_HoA9~7^R5XZ`d6QXd-{#&r8f6ELS%K0bmwU|vX`%>GDE5s_p?X~ zPtd|awoV~&Iu}!6PuF&>Gs8dJ@Eru%bk%;mNh@;S9Zqv0MRo}grtUb|Tdtj-#o8$K zqf!qrN?-8^?*@|?)+8b^ zciJ^kWYUe~7=hD*L;|TcG~+p5==xe7UX|$Pd~Cl!}7cLxcvz|NL75@=siLZ%^%MW?fSI-sQh* z&aR7P8P(g^nh*uR*iza=fpxXGljgiBzU^`#7qgH!q8)iF&*fkiLme(eX=T*{*Qc(l z%}Pt@UKZVP8X>Upyle*cxrYkQb>5o#IQd6jfbaIzNO0-Au3uP?yeC7oX9NEbg};uR zn6pXMJM}$dW@o#~uv(#NWV#E+uiStQq}<0>}aLfyQ9Ml`RNeWlj&!YmNiAT$KK_~+9W>7Kuecy2}`d52F;``8S)iGL2( zcq3U~bT_ZNQJ%SB{2dxcBLmcj=ZYCa*Hvl=Vn7LK2+gq>gXQWoe+K3d#0EGlk{hwt z59{+7$jQBe#ujv^pKc9;6r7oA8iVBWJ8Qz#RRHlm3=&mr#lvrLwS#^JdLTdg!wxg? zTgf8Y<3@TgmwdK;7r4*-R(HGHRe}13_4oCJBq3fJv#Y!&kO?QjhFV}(2*-YpZu%%O zzqFL?LgaVwE$;)z^|@QP<@WSgM20?&6Pi!L%xps7@rAsCr%6SNWK$f5@fsp1-XDC> z-Wx=_`H@{}#HPV^1dvPkvv=}^UbZ24$Iydo&+_u-z~oJj+t0!H{ko*O8BJp35ovO~=+V-n;uU zeCLNaX+U!aCA6GZBtU#W4Lw_oKVRK6E{vsz%FU3gxbtQ2zqr1AB z-=%Xj;tXo*+U-fZPk+}VS8_Xa;MmV8fZFzSt0Dlv&eivC-nShu_Y;U8`C@vx>Y`x3 zc$rEXPXZRue;+|*q9F&>OwR=vdPJ5k@+Gvop_-;jhjF&Em-SS?=4sa_a@o4NJwKeu zlQLQt{y2V_e>{CWzuBa0|DNL}cK;^U#*5=Mj^(*L_3^A~hh9A{L_*atA`tbv5?gi;C-+acoRo{;zU#Wxpkc^jp_&bl^jPs@!5SW@#t@x<~>VCG0JT z@_cIm0VG;i()z*WWT`Ft)W0{~*HI$zxp18IN0foY37@&SnYp2PNjWtv$JA2N zLu}G2mqE@0F=UNqC#l+oc2fVxSkMo~ql36T=5G9oqq^vd<*0Ze9G4pM8?>Y(#{?Tm zjdTvFiP3~zQ?M#&m6!7sWyDhf#8Jlhl{;2U64jY4kZ5)&W0h&!Y~+P()=oO#XI+s1 z1&k8%kcrqjDufs*Acke)C_s6LjhKR3$_cv!M^s;(CJtgxQ7z$`&Gx>4=A(UQP}Q?N;DcV#^7+x7CsLeanuJ=Q0R<0PGuW!w z1b4t7W!1_xkZy{{Y?~SDULxU;Ksu|&xJznMqDrU@`=ai#mBdlOCQuj2hEhS=Q4JZ# zv2f}gZWK8{LNY-}5I|(a2Ba9NV{mF~t$e=mSP)BTVnQa&kW5M{3Cz+=eghTOQ;lKJ z6NHTrGDuSv#JP!bfUP5KCm~q}OSneUl#|xLY6@{y1V?)r<_|CA6n2v#BLf=^T*&Ky z%V9$lXrn%wy;R{kEUbs>d{d$6GLme`=QM)=%^&eLHCAF_Vf86M%O@how7{lGnnIs& zq>*c^W8k$;jM(wPv-n5^U#84&NBLUnX9hCl!db{P%^Nb(47YEcodci4qe+Oj-pU-KMXzPTg_#t`|c0uj1(HQ{(aJuZQk6z z_g&(5-d6}@(X(dd_<=PKAHpu+vCtORv{O{_$rcASuIrt2DnC_h6fQlHeLLU5(-2v~b(X=4DC2C%q!F{fmSp5Q@`&}pQ7?64R zL&_7C`CtabJLxjk54aK_;V=SfJ@Qjxy_4r;Xr)J>jEoOI@+n;};ppCHATRXAoT_OAq#>o5`%37yemw$=Z=V9BN~>+3HpX>13?Gm2919>=HgNGD~u8^>Q#9r zcKg<%(6&966ZYcx|nv3y`jDHq0jfRbDN`2B)rNu&Q@5p7TfguI;UeCD`s@5 zmGu+94OCtq5*hgQ1ExqoYPE6*EIaKB75g#{%E{GvC)J#eHAo(5p&tD3ln!5f>FFf% zKI!QQyfK)?r0XQJxGEnpG;cpZ?qx!_iPZU6*1~oxCsX78J~~gdD0u%h>cu_rD#^E! zHKHG;q93Zvf?sv3hNNmI+No0|6hj#k44!C$T_yL+_&>%Y|*Q#JQW*+`Arz{bg ztr^K5Hw`$+>)5KML}YP@B%q7H(Qq_cnKYd|hoZ7r82Kunwzzn4%#K}UzGBKb!eg7) zE4dWvd#z3qt-A(D11=j725V?QAYN!bK0od`KudXvHV4m=HjM41afsAw(Ah9AYytA1 zL=TUIT6gCyHlVw0UR3Irrjlsc--we^%DhL&j zDI&BP-P{V95VqND_s}gsg`w%l4hBmTL3=t$gdr#|)aWlt5aKWv#VH>M@me(BJJC@> zAVYjG{isMEVa8mrLCY16(!f2XLyV2kKC^buf();sk(LrHb*w1{?VE1^oLOI! z#chu;e^+6BTM!dZZ#qwtkae7G3G9q0O8)fa2gBhVaaxLyN4u^Yr>+!+8>e;>BL1_Jut3P0*=>C*>LaoH^^GudPzuF~bQJ8dPMkwm|c>_y9o-ymTeP zXN%w*`^Qcy9gvNlLgKxslM!tNcT4l=P7=$)gL=6C6$`uB?Xeq15TZ<+VfpHd{lF_T z&{jPVm{j=HNK@73HLJabNOO2$&s++|*Y^I@sEk_9F#Jgmk#OlVdzAfTu^MuuMz$=hRQ6^gR=!cEJw9e^V6z1j-vSO@HzIS^ z7;>ddO%;^oPr-`wqC($NmiI<&;w}6Txa`_gwFC3zYn`E6BQ`6xMANDUAYSxX$cqUz zKUgnjlbQ-=5;v96>Vc>DO+C<}XcR-*(I0QDnL*~B&t78+&aHLQEQ+-pWma$o$QjL( z4XOyDb9G0YHK^UEajD99ZY_PTUzCAuKkl>upDv#|*pi(yeJ64wgKk~FnyLJ5Ct-%b ze6p($H0`J$wt_Du|I0iUI?JnX++{+`j}y9a$_SX#-Nnfx)+d!4A2HaC5|Lf= zm#6G7lCpb6Mhwq?g)r(=vt&H%G5e~oUy0-?#rQ-iXyxiZ!ZK}?f!icpM#&ZIJP{is zuTaDkVUzLsAX;M{=H<8qNW8v@wtfr+8-c3gPCq6=Jy`TGuw?5i`&HIgHCJ7)v#z@O z9mzgw*l~$*?$@BHnQdCugD5FiNz$sWk)~B%bIi5Ql-a_%+soqvNb9Mi&1sCSR;*8+ zvT9s_2KwuXn)%@+0A6xq&u0&Bo?Z}xArKR%{GbTTJ%5kd>wJRyFiURt$DNs+pGo7p zhRo!I%CYW;DX1~aU&MI2%;WcXNENa$PboG0x}~0Os9-mhYc!XrOCzQWG}%LC=O8^AG<<^W z=slxF5@H2GC_=#+IVZkpJP@9^sNFmW!udRC3*s#Xsf%_{=4E)BW4Cpb11rRv~Ytw=S&w%BJ|!L;rI ztdLHEuIU)9lS{t|C*A;M4XlADvWV}r)J-hLvQ<^2|Ii5u9#)D2Io{n5?a0+rMs@Pf zt|`V9IM$~p+FkqMWXdIho;Nv@OoD#~3$}^r*G}Kx`3k9Ol09S5P1OVPz>Mkw&Gg__ zFr#Bn>wj)lpCZV5%YMUan%KtN_+i@O_9K`wW9M6O7MkENRU(7$LB98KDZJtjw}00% zU1lwkhEFhxJLLdiF8t;stG0=KYK(DvKk&boL_s<`yzeLGaf4|uY54Tl`r zkvGp75A0(DJI#*>x^r-5Bywtv}yTh{;uJaEF1-<3hJrL6S}6$IrO=|XJ_ zLe*o}i=m}IuUuniv6nL6`xQN9_j=(qUMLy#3Kgbu4D@$cf9{`%qN@_R=R&U=+E&vZ zwG|iw&kSEC@a@Tb&Cy5iUO+L-|7o&)`n|otG~)_(;>s2D6Z?Vzt!NoAp*97@wpV7b z*AA+oK#OOy`0y196jXk5J1@?3G7<&11Ufm?J;prtLlo~1`6t|>*{ zczTwD*^p5`fr=IKbBJoS#+}m#E#;lR+_X)LuEtqit)c^T-I@SCV`Js8!tVyZ;1QC9 zgI!+`#SCVKFbhVN4x>$JU5OnGl}^m_e^|oGktMZ$!poiH34pAF35ixA?~luowANG5 z?EO7I8;`alcn(}KXk3lbr?-wiSV?lP**ZCm;!XZZhB;r8aMz55N3Na}TIdk8)@GQ& zRM;lvBf?eC2O(q!%ALZEY{Y@*;n)MMm~@r}DU!k5R`46sz2V9$#97E7UHA1n#&vzu zIH#i+HiV~ca?>fW+R3DrRjIpUbbYC39_a;%sq3O#7QQJh$bMGV;0YKuiU^B@d4m!8 z3vW>?>&n+qF@(_8lucDPXh^&sJ(DTAf)Uh-Uq(#gz^DjPVB-hQ#^enNmYM#9`AHx^ zL;0ptFjV)?;3@ZB4ucRk>%IyxRCNHewjBU2PhQ3Hshw1heXjSlE(xrxb;2%akGA%( zTj9B0lsXrn0>GfmJxi_&Z;l@)ajefN-3suaE#5JPJVSYTB~CMw#)+ zFL8|NHMgMeOI7k{7hdZ>6eYIgo5yMxYv{(aRCwpX)&_q7e>60haK0}`$VSB98#;%s>J*e!S?H|fQX%Q}thkfLP=gL|&#)MA-%2yFolVXKRR zLdMu(eB#4p_e|L-GC0o!IcbemOHr3o*TCLiB1s-QmiHFOL%q(??7;8N6tM!Q00gh@ zrG5s}59%nohbF3~^9+B*`3;~&u@lzq7H`=R-m{`iPlM)i^$JKkH5j!dC_&Wj$0alj zEW}kw{YP()e`SZMZu`KIjN1|)b<7j~>zF@6999$sGGPRX;< z@x`E2%xv)qpJWkIb85WOns-y=6CSfhid9LLGwQrFQ0?Q5s8r8qc0T%MpRvP=7&$q= z9#l|m(+9ExnUW_HWM=^0ERU2?azz~I;V7gK8a7V#)Tn=%xhqw4oQ0?;pJ_X{nw}GK z{?mo;KTy;@tx;*;d+S^qG@@8}IXQWGc@ZmIQ_^neCzTTEp?`CM!4*&uhP#n}JYe~W zs?2e(%rDGZcMpnPZg`;75&3jQKP?&2?^`H{Cn$Gv6rw9aC>avdc=XNJ!D1n|>yv9_ zhGjc;4{#k5QjoeetL9IsWeL!tM{M#$L^wZjJSX{-ewp$lYB>n6@jW^x1XGF99=TzYXf+7`&Te44 z4JxtCEixgk=X+NmH)_Ea)*RY~)Ckj~JUQehcoN3Pf2|5cxKj8ih#t{@} zBIuK7svwc+(MxYc3w0px^0&o&C_frz&Wg&YLdOF3N0n0BsM_q-x_N3khApx@hdUOO96y%B8Q z&4T$GLCL;ZCx8?XSd;<4+C|v$?T1aP7WwuP@yFxuH)k}u4U8J`w2~gkpfbXH!ybF- zihsdI@< zCR)|UgQ0T!oS9BRY7fIz5$5MgK}g0Ofl0R>$MGauN0OFTs`DJb`(AKcR26$Oq#6w$N+rK@38X1xA& z%o=v*?%@@Tdf#{rxHoDRf8Y>{N;ep3dXT9TML4VAT&Rev`Ltl z(0ia58N@UqNB}8?z8%R81bQQPQJP>+bj22OY`}Ry4wR7w{$kleVHyZ<4ID|;BN!RH z&E(k_DW5AK5UU5YR|XjU&(-B)4^?Sb2Ioaoq?*(v|AiA8QGAnp*<{TY zka6ZTPzQcB8OWouM+h@yRV&a3gyGq42K-tI@Z~kw^BCME)&LF0bNt+RNx$m2REruP z8%{W3kClpXtOzyh7=hlOb?fin3tQGDuaZQRwtjZc6HcIsjUs6yakZu#)-u_b2Y`&3 zV?EbkSPYZ=P2JZ-!gesSD=J}M8QH=`kyY84>%9>8(|adNIxiHYL1{>FC+=u)4o^bs zAP(%7Y`fTz@xU|p_%H<_(}?WoL64KhJPA9xNbrW6W0x9_SMyjIrMjrZ=7%asrt7o+ z5cB6;E(nyBm7#ZG&96kidM2FXkw{L~=*+4UuvU?5hYU>dytV1A%tpnzKHoabBi?Lk zckDQX1ww|5WLFvwrZrAC<};NsB`9cA;tM@vd2qS(nHm?l_erszj;0O&%NMrW3NzD* z*B5EF*vXH3i9fgx8v>(2s9+ZiER`-X*FGZD@sNFV=s(q*UV}pQH!-VFHmy zu}0>^AVhEY4Dbbl+nmG~J%03+kA>kc7P1|@RbAg_eDG9r!6+)id9_|kNrYTAd+HC} zl0sz9-p1Ty!my zkx&+IRz`y)5-BUaf_x3U`~r_oi70YJvpRiE#Z-a%g8KNb32AttJThKFp{*K0^CQ}L z6~#puJ*b>cDo1mo87cPUpz>}MmE59pDd88}-)x!V0T_YfA(@mK%!SASgNIyzM^A%^rTb8&H#RLccK4b-3o3v|cArfG$C zj_s1ebXfRLtNpVR9H1zraQT3P(;YJUa7M(MB3eeHpqf6$ymi9S7dbB^Su91}u}mKg zKw1(+ZWNbnX zvIn;e@V86+tWu#@0Wu3uA=_-dDV(F zSj8|OFm3|D|j5jfdJGD)WGy|RufD_rE3?=DUW3u z^38h?Gg_v#VtLWSP;<)SO7dV<*|Rrxv& zwvvp8%|<*53tw!zoTW4zp-(X_1h=!50LV@%F1vy~5_ZJ!IZ**mc#KT2s_xq{8Sx;T z+Pg&|X=6rmPCXebqbv2Rz@YziJ+>zVFE<$#D%*PEugUJ#tI@-U| z9gnCyfkEVbLFEef%j)+qP zM)d@k=!v`2M93J4=qEO{%z}wvFE%CmCn+ZLSdzuClXNhmym~7ds+J^wvn*a5ocaaE z+EXvCwJExf6w$_tQ~qrnIq^s(;>8np6{$RdP>nsGq<5ufL2e0~M832**ypXav6#rN zsdK^`w$#IgCfzVlP3~lYw{TC?P^_fKpg7dBu9W=jKK^ctL{hke7wq4Arkl9{4=chn^WBm(1IqaJokPj)% zjK7Yl;Jbqhq$fKS&vqrG*&Bvjq2LCtDCU|nf`_sus={%Chcnj2rW=-DJ;kLS2mWxR zv*I``h;pO7oWoeO!p5!xwtB61Acy9@dolkyxd43Y*04A6zP5S8@NJwu#Q(ziac2hZQ3N}uAXzS`=T(zj&t91HrI1cJauxgvRyLIC0tTJnil#B zN#yIgOKwR5`bSDlaqscGZF1vry`YI(!1Szik-&KxOe**Z0h@A1Zc!WkAvS6ARKn(r zlXb0U;)3+}y5V^#(VNEoQBL^^Wq+kkxSQ@}<=@af0flZLL`VtwS?|@y5l^qJ(fLoQ z+&VvY%MGK&9qi^U28+37A97f;+8(4ZdB$3+Lj!Wt1ZVwKqG9`Mr{}pVt%{3-=20=R}Zehn*+DVO7RVY+StuO z=`L^b#Torgh&so_qc}OjwEY(3ty^>4BOk?;8)y5JN4?M6)N6tc{G>}iPrrlyQj)nv zH6ugFF@eU}{!BYCSmm)7H(nSqga`VS5f2vJA3~4m8Lo+<3^MHA-ux$260>H}v|%3n zd^qvug|+6pcI!N787>00j*V&ld#CN}Id%kjRKmx_r^T*7yX_kD-DytkT1H#4+bNxC z&F93k1tl`w8+)V6Vy+UAn(r-Ph^LyOm_v$q%o%W0l#2@&tcj58R*l`s4M-qEOp=gy zCOb7HrO-ZtQD>@s&eNzzSUKEqcd2^QcmXFZ0Pl-Mh|5}^xb8BMAT_$gCsIw7qS-2)M4}sx|>cBp4xIt^ixrcF-bIvw2?K}h7(*Z!P3Srw} zpce~+{uD?CB6Od;KoUUgM}xddF3DJc8?q^+;83m5#Vu282*$$h0$wPj+oe01uvcsp;``08Zg(=k6brE~_u4pvN_)_+>IEjCV6PfTjLhwH zr#LV%Xl?T#IM9WXXeG9H1X~~SJqz27R2fbW3Nu(@*++N zA0kz7g3LUbvukzcSSfyC^V0=pEb&KuHoCMP{F35~4-9bC;nsOd@>Gq(+82|6*w;)+ zg;JX15v>T=>kr1a7H%+{CZooqlQtOd&Eav zy7t3ltbcWRs|hB$spSQ(S6@~78M!M+iO*Xsb~D*Ec=v~(Fs6EPdt_mAxmC+~sN~w! zu?u}+tKEd)EvlKa%{fEJu42;KGSiA}YIA_r#EmtfvBGfF6qh@PeBl?q+eR@drwBc@ zQ`dS7GgB3rir-5U{vC&+TCU%;e?6zSYyzrh)g3WZ^7zGq}h?y zDr@VpQN*BC?}>%H(#Oj|$_`w_Q?b7S0D9j^As}w%jzfGE@Hvb%zB|Gk?CJDjC^w0B2aQnYz#5(;iWvjl39h7N<54qi-_8b5mXN2)&!)q7Ki;i9#_R_vR9l` zyi{9m4IA%!s94Ic{dM3Gb0VN3!uE3Aruo%V*FJLR_>;w;50(Jw<;IgQi5id z(GCNFvu?WyBv2u_A8@18yNX88f3w*uH9ip5)X}V+YmHM>6W8%)R@ibOVay`iPxZi! zcatrVUJSb<2{FSiGoef-Y{^oeJ{)5!$Sor31fqD;?0J+VR8lzIn_FkKSYbrQ*Vqe6 ztAjXKa42R1 zdJPQ$uKbppbRfJ8D{QoB%P5mpD}#wb;$T6OFP12l@|_H%vNJW4jOAF}s2p+8LzdhG z=B%0qGv%zw*xgKBrOLNqO%{v(rVXk|yBi#TE=-2ejk}4{P2*!h*~`4jZ>Yfq;z`~-sb8ZGGD6UtBlt|NiPDQe-sifMGP)Ft$|gPzqou0jqL>mO4*vtl zbOmWgzs=%@Z9$i@3{#MQzX7dTT*=H9xbCQBzM6_rZ&) zBk9&c&oBoD?A+{qFVi>oCHGXw&A2x9hY}I|SNIz0Nvf?>i~&jf0hcb&)_e=ih`Mgk zF@hJ;&V30+Kj1syKd9tmJ`^%yFX;3Z6~Ve2+DiTi!L+I-g-R$Ob1*@@9A%O=wGR~3 zYVYr*UaRs`^}uPB3O8GA#C4eor=oDs*sG0B6RndK6GfB_odjjehWrc_r#IWIen_R$ z{4F-bP%b#Up(|zW*utnNioL>BFj$WU7=4>kVx&FfxW=+K1<6SxV40(GxgGY<3~oF( zS>C+)aeFzu`0n+zuUMrtrbkvk~7zrIQnJJ`OHttS`_Mzp|@!s{_Wndp4l|@Y3i_->E zj8Wri1{Ug{p}s7JK+*u3k0=qibj5wCmfsvfzzwpYO3+O=(mEL-A?yRuDVB zc%qz*6nN6|7SyZk4XL;84H;Db*Sqz@>Sg6u$8Uwecm&u=$fGj1?^8E^o6(|8EUkgL zaY&bOu}Yenaeq=Pr{B#wjoZFG7MCdEPv7dF4Due1tok3qsC-&(kt_<$>YoDXydui= zb{31odT_a~$Q$$4z0p+iJk}94lgqOq{QT7iSU~x|7evpw?E%w5-Mr(ZvCz%SyXCCb zvCQS@z5+xhOf^*_L$9lLN~pHNSdZG9r_e8eCB)$8=N}$$e%dA9a>E|?N&2=H$8n{E z)eTpv3((q^+B7$*P0$f{{hn@W=nH>co$w2vz7U82jf#s+e6eH050Is^W}4S^FuSCl zsi*3K=U>@dgB!ra@VqHMOjI?u#ImC67__y}+^`R`TepCUnDBChHZ1$~I-H?mmn7bi z)_N>tOe(#65h~`!(mT3xYRB@u&WXI%Y@V;3zuEv?f;0FB_XI!z3{LEtZe#@92B9PT z8B1(ta$w?Gm-QANG^HnT0$9GM?VBj#%`KFIG-{$8?_EHy|L`*aos@MMeWR$8N+u3$ zsXk3|mP?*M#8Z4h53`?h+g8C;R=KhRR|B3zTVwe99a}aflRJ-eNnsZKxIiC(qwd4C z3E9mJ?Ozz&OZPYOB1olT=VT}8?=uql1hRD%%!M4JyDU?h!Z|yM$vrCbuS^f3&9Y;W zfQ4R%K|$8Xf`OjSOT>J7?4{@vnm<=_o{7`u0W7TngdBN6vunEyvMl0y%Fn&DrZ0M%= zKr9v=JLv(%pr18L{f%atoMJqowpalG<3|o(l>}nZ(n>L)8VuPBFn-YJnbPoYg12sc z)pnDbBu%c(wlkruYHE!~zTTz!s#rB|FlBvqe~*akp?D{or9<()tP#Ce=83=Ga)wB9 zw$SX!6lVuwucFSPD9UQI@vFPW6{>uKInzuVBT6;vU9Nnkbr&Jtn_5x5Z#C(be0Ob; z5$%lZ{076qla;y$3BYxoAMO<+@PQZsB9YgQaC%W<=_79!)b=)dyX|UB{GfH|M1o%A zTFDlZ*F@QS)Tzy`q`LpIp_H@V=T5MiTI{i5-EaacbiKLI-l?i3-mo#8Ql8PlA#2)_ z9?oS4FcvoFFUx7(dUL_Q$CR8C$X~cDExv&*H;r(9n6YDbYoq-_k}CHZj~i{ zj|lR)&RM=!w+2ZEY<#ATIV|k{{{-#!IMza0*Bf=3XrA5VDY6y7S>V#QsfBX4zR3f1URf*6YJ2lWedJ61z>=*3 z!lRT^C=B#f&PF57RP=|KglJMWHa(e}eVmw0+{`raL4MwRu6q1D8a(xE$K{c6k<{X) zB?Vap8ROn(8G-&>m|WTOaE~>>VDWIRN>TT8c~#EvxY)rFCR7?86k4^pIecgZ5DFcf z0-}sBsbiQ~Cb8tK1EA*^0+CifWm$dY%R8K3FO}#$b17Kqx{>3B>TNxsxY~x1yOpYY zxm6zNW^|tq)iZX`Uw|WT4>BjMimy{~oG}JJgfI7rgwxl<{~|Am#eiF41+Ou9Vsm_E zc|e?x=yHdtUL0yus_z(>`X>oK2jn`@Qe+K9#z;d_L-a6ukwMhmp2$qqbx%cRO0t|H zE@8ReEK5msn^7?lVn$^|*uuZ*v0@R(Sw0cT6f{u^5)X5hcd-Aow6!7+lOjaRuHb4N zd0DmA8uo$;fxM;)&l?P9M!y^3g-|_pm8jw;g#K3Y{Sx5dN5uLjQS40mT#>oOiNkHI ziHjq{+zMMPp$dIw;=9BGFZ$u%K|?gB9j}VhA?;X)D9A4nP2Y)InwLFM!9EO+7|UWsTtTR zhx%yUp${mV=;pixqYST0f(NO;7qjeR3^U#b6bqi@@u68TE{8#N>~;=uTJ`zBtQnV1 z{gI|Ke-~1_{7d&7yXLkgUNZtu;;ptXT(wt2t)2TyZQj@*^VV|8L>7yIOq9aRp*EPJ zRc7kp=BZXred)+*=;rxqE$Ib_EY1Y?7j!n=AT|HQ0(ydv7CB|Rt7VW9HJ=#gtuvHb zHseJ`&WTW|Lch2&bx9paoMO9=z8o*MhCVg=QuFeLzN|0tKnA3$H?5!2{G<~k^hBjq zH9JwB?MP=@=Sx&Ca1ZkUH>}On(DS?TO~w}9vh z-vYeA!T6bEh$hcX2P$whuC@)BX<^Q6P*8a^U^lnd13EW7+|aQDo3Q$%X|n?}+~O{z z>51R}5e&dB^sUSMcMFWr{6CLZ9gGeCd%VhH?Yt$HwD&@-@CqKp2pXM~8-D508ri%n zMXILa_&TeJ7R^c$Mnr=k0H8pWI_baXH;oJ*<(4$FqN))oYEb6w<>mFs`G;ISPiM+0 zH9eb(Vv|y~De<};5bDQkNl1emFbdcROpg=U&CJ7u9+*NKLFpeAov`3~X znM1SobV@})Kz+E#UWG1ZgqabKGsN5rxvM1^{vrMKqJ%G?ZsGcW;#=oXM{n zg;EYzqJsMS_K(n=EiEj=lo^U`~}qFfX0a&e>FM z4P74}CnsShWFHWwj(#4^Fi&V*!EffGM6qtjkJ6uDvj3m-Qw@z)6DNvJk zF2Xr{({5UWq<}@R)P52UG`y4ko;0MlAPK2W8R|hdvMG@PCDl3~U=jyVK@Hs#i)dsZ zte9}B6LA3MeHkb~cc^4HTIsq(LR=J{{7g7rqqPHOL=(!_5=&=lae-E}5SSqwR8*ni zW%@c{hx{cgYz((nT7}^Bfa3kXHBR8n3%BG!9t_s=aII~rO~oODB>{50H=WOnXS+$L z_@*mMCaB%H2>t;WyZG#tVuH-T)99I_ugt;M10Asn>Um?hkFW=kKh)u10NuN5 z*6(O^$_j%L%h-zLMvN6K3ldKWiV1k&w3Eq7NUD?BNUY!lfG--SUOwV9)EYRBcmF-&h0@Yuee7Tu*>f;1Y~aIbsZaBsm0xPf2Tjz}A@42F_4gSSTz! zCqnA3x;lLR>X_Pf1i7ad>lD?ZJ5iaB!=v)VdFZ~KWyKPv-# zP95+hWS9`l7Nk`C=(8Blv;VUz0YROE?s{TTY?5rWLHm2KLHU2Uh!D6bSwzQ^Z_I#q z7Lp)a>&Jv``2!U(vszW?fzPL__-g%jp$JBFQHl<5p?Knyq94UpwfZs)RzTDMQdRVn zu09aX6*k2qbyJ766O$TxYfTY(aFyqx<4G^c^C?QikXMD5qlqKB3i9Z)dA(8c7To#R znjj_IR4WGBwfw~nwn(Q?23wSwc?DP3VVbH$zTQx`s2-cXu{7zFjeohb0XfcL3%$pxrr{9rO26P?#*!%`P+8V~Drhp5I z4PMx7s0U=4UA`fJU_tc2eu2sGbBbg@^as(jrld>?4sM^fLOnm)vFD{NL9l;Cw(60q zm&x~EMT&@iCe-3Kqf${=S(09fLb-68KdBQEVO|NQdZ2_axs4JIC?c+W3CZVsq?|nj z1uBG)Qa~NX>_~(;+ANyCYl@ZMu!f`BUeNO)YY=^gvWrs!ws8% zQ^LGpg-Eq!@4B-EtcH04$=tw-QE=+>g^g(rcWjQ=qYj>JR>A!JaXg0h@pvLw1Gfl+ zmIgrM4}0J;#|@#G90`*B#@~ef6rRL zQ=|i}h?VVhMWad~6p#W@?!EpMwh(GZaM$N~I*`;6$}MR2hLeP_9}kTakP*C*6A0BZ ztZ{K)0|Qh$!~|GwxPrg7*!Pe*sKRQm-GuMA)PaH-mItGHQKnU|zWJ^g_TS##5aH4T z=(LQ=xx{}Szthl-g`)%IykEDYmM$zE&~kHg^F-dSPXbnN%G6XX+VTbB?q*`);&hK9^pytQVFu~c+H0gGV5h2{6W4kC6@!w9;q zEXpm62PU&wjePH7x&fgf1^@E3fW+b)dYm)$*DKgE{Mdjln}PrKu9U!tWcHjf4k$vQ za?P{f1D&>|6mk!(-bgdQbUk9r50FRvdP4m202_=HA?frgykK-~iu5ia?kG*WQVBOB zT#IC)bL38-pfH5bVAc#Ck2ytu;jxqYzb$JF6x*DBpbe=jkXG^Y%wDnDe7u?PNH*nY-0 zlTs8jX%R|*$i;ZX?s=gcvfmn9%f42M;7LE=f&HUmU~|+^@YFCG$99=^gCj)d&}#G3 z^iX{v6GdZrHf`C6U;?HMf~?g7Fl{t*a)eHSu>C55<%@l8t$_e8$k!rjfc52*#Q~#c zs8SQesJ%gj3mFnJUWpZgfrJRo1`vZ;cxL1fkhhvHWf7u4?X;yHFhv@>q-M_Xo+9c^ z1_tYZWnXLi^uhkOBGz*13<=>%KJn|A8UWPzu_3x3v52OIIj$-TY&OvQ#G$4sH%i5T z{>(bm&la|3VV%wsnVk@tfM#vthxboufN|*DtjXMhN$w>X-7s%szj zjpTb^N7FqJdRlUdIB7I(tgUCmYJ;<{hS zwhCh821ut#J`oK_K|R2d2YV@+1Fhjs8!!ut-INs^!?nEQfdJKPnDAbK_mx$oG+Y_3 zlo{?N+ps{(zd^6*#5#zS|H?Ey(p0a1hL1GY z1)}L)sjXiOaSxoX7z#l+Q1nmCCj%N_#VE)Nx_CF|!z6?Pw|35k(N$TbE*A<{*e=tj zQ%1A7m1O|ojOCJ$)~T$MC!gCk1lOe2nIB!8*Yj_QZx?O~JJGK-zy~0g(>kurmKv22TPan04+PC`D z@6^hHvv!!L4#qr-ZzrK=HXV_f^ziWTKAa3rR+)HW>uL;x=~CY}ex;9F&X1)r)AxQ_ z`36nwrt3o@Ym5$>{JDceS76@HZq#QF%!OBqO!+a+a#7fiE%w?Dan?zK=7t}EgoYO3 zVGh(0!u1E!Uxq#|v`)3rQ#CwkThJqqKbQ=Y{t++iD!*?)2wzx8Pj5UsIyzguZ%SZ& zJDW^Z%;QiQ8o@gw0>NF9R1tfbOa?nLC}a1(b9{{WIViSrfr3tb!@^mB>a#IjlXkB86OupzIp#D?}vf40_tDk_e zPDFlaizX$9&{U&cyToAkVVa!QrikRBcX3!H8ih!LVfp&vbq0s(!8f&@8q{lxd zYf6z0{A81;7?!At8~vTZ<~Bmd^*Z6vVm;X_Z+JOpY%y3Ml%r+14@Y2Viy*9!763rV zQ#7TyQZ#{d$WbOkL{%Rm2{POG`Cuwz;!^R zcsFrSnU73NGN+JZUDLNB6Su&*5oC_Ug?Jh^kS9{**UWwrREh4m8}cd=KX zEPhbyS6cx7l4#j7{D*bPx!;wTx1zzUZ*G(J8X20CF5F5KtL4H{lyf;MB}>l8K*5y& zOZ=?Fka!7MlYcH(%RTawb=WPJPRgN(>J>xZ(6cBI0A=L>xxCk`V8kfrM49H=5$*|! zgn)}}#t%}J>4koY*8k!$50?9OmXwP%sjg)BguL(=y zPZqgR;?7?QtI-LU(CpPs9z|HBGZ+^A-H4v zp>iu7PLH0{7$?t!71-^o{a*JGLDd&{coEFj?uLp(*@;KnLSW{l z(x~Bmk`7%~A5kFh-HCkYZ;`7~nX?=cE=J>X{%2~Zg!uNa7@|GaP3uXV9Um!fc?r$H zhJ#!h8asaJFm{8J#??^k#N?rVf-G8t@1*E)Y`L6@c&zERLjk9|z*g_$a+%5R18hNM z5BK48L+O<6frzAkA zY=R~2JC$=4@1U%X8RJa{sy<7Cg$w$t^(BB)_ure*SCHpWe9wo}nJs|cHUc;`S0uBO z!HmaJ-N@&F+CZTcMnf<#V^`V|Z2_?UT*1Y$G(d&0Xeke{)F@t)3pY3^52Ezph0pz5 z6Vq={nJh+JOVoXX9@f$Ic0zP4r6ej5<`N@{vMkK-LThq1EQENqE9X91#M+Y5j4?g| z&?UCr;h_ZWtDv}{tG+6NN0He;J-C<*fl%}P^MLks@Eb??z<`p#J&@4jej)~xS2xG! zlki=IewJX3maZpX5Gzt$kqayN#a)Ea%>+-!aE&>YUeN8XLymQO`98^m=CI;93M{a; z7fy{X4N7>x!X8ytZbkeNk*-#86WVhfk{JQ=j~mqQPU`ALw?{yu9Q04Gk0!KrVXF2uReTyq&7g*PD5ASR3H__BDl(s5VnIP)C zJt-jWjvD{r(6`LZ7%lX<@sy}$#2SvgeNk}nIjtWD4$neKUl6|pPlJxVB`;b^g$Q_s zwsp(1ix?J|MCm$>WXhGo3R7+~G-nzL`LSHLAbcXNy8!J|p-Yn1DzU%^gTxhUhoCYC zW^9|Gk1o76N?KO4B04mI1otX8*~ikR*-7f9Gm1)Yyj#9tM2JyPGTdKT$|3TN6*ES} z%lXXf&}B5^T^XM&it7_MMvLp0D+Sx@VdZ}?&9Qn5t!z@>+l*GWC++a-0wZbt-^r~e zGYMz0H7(nkTtrVkt-XiiS52aVb{B&52xqFoy}w@k^Z%VKjBUfG%^*7&s9&$5sW*SQ za%(L=`p<2qeC_Z_9D`r-tz01^{F)^MQ#F;JMVIIjs)F0THk}TO}93_ z?>7W=Trvq(3_30!h3BryIO~34epPXJ?bmg+Dr26!WHNLhQ%;dn;2T=dHL3C~F-4O< z@A@$^s>Lr{<&CtvRYNuW)xs1iNPQGfY1Lx5672%Ac(C?;tZ1%+e8B7M9wh#> zhV(X_nM;&4nqxE&zW7O*C z%<_FloUDlm`dfiw>hdcQt> zWdHWOBqjAE_lxm57)*B>P9yf~xD4vx1$oZFKNLb9i;w_deR)8ieQ4%p0Q+AsPdk(B z0ON%>SUR*l0V<9I#%X>EGoN=#h**X8*LcS8sdTv}oj%U-w{LvV=^vAjG73Kv!BYtj zf3fsl=8h(Qh)%_81@_q)sfC&C-R~FXWO1ZUO$8w`WE5lzwq=EE|9J z0dzoZ5x9<&O4}Z6*MyIXs_R^*+k`GV1TH_P`G|zvr@;cJBHpv%*Fx}VoublLXTo4op=mru92H)`7dG$%L7S@UoaO3@V@cCcsgRlKt*ca%*?e_#th5mei)5yNFv`s@rWHMxCVZ$MaY#J7CSh8Xgw$OjlNEVXL zwOK!uUzxH&B@7mb3;L;$LJ7wGlq#hQG;_rLL*&gn4U z>exxl|F|2_PX3`fvS;bh5F3{F;E}XMJ5>Feb zb6Lqu;oejw=RtJP`!^rADK=D@Cge7SvX8rex_ZvwQzTSQnr1fa$wMTTe1lC+QXF-@ zqD@o-mn~^G?@M*6lYwavGHpY{p)eIS={POsILIHRj6F#rLd^AEJMG0iC}ek)`p3z~ zXVZb4X90EO4j2*LORtG&A$nPc@~t=CF^>3d&m3-IonZNW&I z$D*=%G%_!lR3~8QaUd=D;b#rAYog8x)L86gMQ#vwYpk^J^XMoyJQ$6%g)8t2JL9D8 zw|Kl3wqJRZH^r8Vp~C$}&(Mo+na6#hRCP|v32BG{i)aliON+dlcX-$@>FX*< z#2UxZoq6)5w zI=4dF37we}8{6-SXQuJ=LBT)A2>r;vFDjR}0?LZPm%R7{U(zQ6WJz7ntwo&M^`#eD4>naBdfqRG{!ZjkT z?52AbSAjaGGSYk#zELzx)<$`*kgkci%W}j+Vbvz>h z=sOZsn>0X~GxXP-+@$uXL!zH>@H~0TBdccRO)j^3ng87)2+}46!Bmsynj5wV*$2{| z*!D43B6#=x64UgaC?*M5sHDfJqF)29v`|#am%M10J6zRT#JBSEYxD_B{#+vfHa3h$ z|MscKb(H{{C>{O%@b4XMez6kI;KuS`iOw~54{S|(yj`t{%Sk7XkTy<%uEm+2D}1** z7=rZvhPWgvsX8FDD;$a>KAVPwZ)FqK3y%jJKvCt7oKM&cB*(8QGBS)VhxQg<@5w#! z*^T0=n*!gez6c^WWM;{~Gn-!Be`M;@5i-*jtwZ?7G_`e`a<0D4N}9bmuexX zy+5MRpOo@H&XvP_KpZZ9=N`gjANFA0F?o=ye`|XL9*x^xt2;WDDZVzHw9_Dt>ULg_ z3mXJDeAs2rdcaRK{E}b231?K!q4UdAz&!iI8A!@LHM-`555qFSfh=RsAwVw-JmRg_ z&&k9Wndj{9bjS8`#JaUfr~d1wu&6RzclAOM}wD3>8GV-R1H&keYyU^0h z59E_$YgL23{ev2YTTouekUj5K^iPtbfQ>XG7;%9--2M2DO|Adv_kVNf3)YTM+01++ zasLO+)PMWe{{m5ceFsltLt_h5eSJoKeM>t_7kzyOV+T(c3wt~IUmX9SYwWy#bYuhs za?uqfiu9EM49x^m4~m4@2o#AHW&j~4-kS<1Mq~(fH^ETO`ioLbfSSCqO_Y&?nX9)= zcx6^_M!9uhfNpzIRDg|7SAhH%Jv~1cWqljxFZy52k^=nvzZe_I`1NY@?=iyV?EIuH z^Zy6|pehsp@7kB2f2_BhxaW3w z1SVE9YRo4Yl8IOeod{|o`lCm(;?}B{#CgwYBXl&PzpEFw`Nr1nw9n{Zbm`I=7|ORh zW^8^r8Pj_cod*}57=Go~lfTo#d(*jML4O%vH;B)=neDN8B6P0c!vUdbjq=`H%V+sO zfav*=EO2&9zrClMCfHTBcB29CO{#F9q&EwDpO9F{w^ zoK?BcbrOMsn+vDtje-m6qZ~QAaR}@Ja!xdeCC21sY!gZjz~dyg%oL!61DVO;NK{+z zMWhKGHhIP)WT9lCW`P-D*)L{CCI_4tK{Y4>vIj!O@ELcEIB~x{18iK(j$$MaZ(7av zVS7pZT&fPtF^hFGs}C;o6OFhcXN~TZ<7-2&UHQz?r`ZOl0Voc?0InEd*(pp(;5{7cF894mS64l#LEBzAo%A6j(DKBxSg z!>WSY;8+VPCPw~YX<=jRXr}X7W3N*sPV*p!1w0y%&9UFf6V20_ywU15+~0pqD6r1J z7u4pV|HP5n_nL$*F*9z;c}wc^2%ht!E204iQ~ws{=ojax7I1L}cRdL3(#=Vlrk?wq z#y*x?4QPxINSl`Wgt+Tf@hbuQq0e6s{v^DzfW6+PTGYjB9@aY;zLI-?g6~KRzuo}% zZoLR(6S1DwZ0u$}nN!uim@)8~N?NRMvgM&*+_yQr$Y*}6uuF0nP7>ADs;m(f%lBy} z$63-@6w`!no5B4n&+<&L$nh+q&Ay5v(tCm!q(|BD2>V}`{(q3@gtI!W2Mz>e2>pK} z(ZtpCev?rsx7M9QN@TtXyq_q-pxA_i-gYUiqVNp}{GUEUjQ-dlmanl> z-e!j7*Ys$Gj-e3!AkTPr#}N}3t(^W;a*%!A*QS>+MVtxs-l zLa$ZWT{!c@DS**nY{#%L%v@1)3X8nNoFdJfyv3X}SWEASHh8`1els6kov*PAr+R+f zdC*zjJ}?Az1$D(aIc!BTOYfJZCw)RiWBadzIvRvkUgvUevvJTCL0t1w^T#1c&DamM$@qn3gpjn>#psy1C;5azF6uYL7K-vk69q`S$eFVHkjU`eS7Dh)f8aI?pfO zbgq^?HUrk~Wz^sniwW&d!@Kgk?!+vGJ?|Y0#soQ3kB$!-SO>-^>}$tB=xcM)1XX77 zdNRJSB9pPufv#}N5#fG|KMrFm<)Rogo{$F?W}~rW^6dkM5u(h{M$3*{qj=^hk7aDU zF;Zn3oYLr{rM##fRIk9W&C=r_ASVW-oUP-hIA#DpNVo9P49S(@+Yk{tU>8U~*Kkqz zjws3;GvuUY}vwZLCYmzl_+T2aJ;*S{S-2#^%V&U)_;}s9z3M+ z_`L{RU~81o-OMwoT@C}wzfAPhmNck!eEq1Kfe$BfAxAjd$8$K_F+ro3)S>~*F_6;m zM*zE4!g62-SkHL?R;DI6GhzpHVqgA;#mVjY^;e5pSat&`#se|xyf0EHU24^AcYANQ z-_(~%y3EG-i4>7%4)t-msB^fuL}H9b)(kv2*yfF?W!bK`>e@dNZx_<|WR|AxEp;m;;2(Ps>KP{1D%dx2xww!O z*20JkCMIrzr09J0Knex*nQY9!IFliMJ}bu&oE2dex9Fd}2*)X|?gRm;JU$(7l@0ho zMH=#*rk1oK=Ol8u!MwuKZfHy25PdY&PDq$YeyYR; zY+O*b+P!nsx@-3C6f4!9gfl0yoQom7vh+Hyl;X`=r4U*vn(d9voo8>&W=hL?tQp^6 zc4dJxTJdxEHRNjc&EQmDKHoANHa*K>2?<@L#br?m9@P6^XPDR76m{eN#PMz6>G|#QGw6ViFTF|aWLdIhg<33^2Q5q;& z#RI9uVry+USDtZ-q9sh>GzXF;g3xs`N%Q{S43z$psq5Y^Y*fW&P7Z|-OIZ#cTE zlj;8?PFKDg@wggUgck$*#cPv(jb$Yl@c+ziP0Js7fc{8QE52V%_AGiXk?+WEZl<>WT zgEL>WOphs9^IU)%XBr9i+Ith&3Q0Gu)Uj!}2CFz@$A@4p*SJmLUZxC<6Oo_L{=yEl zj(GUAB~yoJulICnSF!X*xK%N}%9B3+nv*B4I)zhkUxLfz_n;oA=Zc;`grFZO-)TLC znErpuOP>zjp>I9K1WNPf6@q85edYYs1Ym14o(jLS7%l`(q2CerswepL{C#PwdkcD@ z3(Q@=y((0_ZhQF&G<9d1=rx{g+AE(ri+YZ+8%`{E#8(;te4Z1tU5UnR_i4t6CJQ)m zT_$xko?86=#v3k2fz|QvVZ1Jv-X(F+xp~he$2c7}_O^ewTM~a<@}FYz#s} zBYq6^opqfj91f@W-bPxk+Hwv5ax~|fa+;%9S(@#nvN85vd5XLMqA}&j8dX~0 zZb)Wdr6dPK(B3{#tF9`^8OB^CrumGGCmuU=R-w{%!{rV|Y@p2hS7ff7`Bk^d7me*E zLlt=W?rw)Z@Y=wf?sif7Llp`cg?WMZ3+%ndHQ;=US2dt^0(vs3cU(@h0fl|xJ?+%` z)Q?jNN@F2C&MG}?v#m@=*|5GRUrmf}*a8L|v!s8{w-a{~ z!?TL5?b@w*#4{t(YXv7huyoN71#Y|>C=g{Q@M0h??hOvToK{-$b${G^GIncrTcy!NB#FrlQ8WK|%op<& zN!)>+GKL9AKEn!ULIu8?iqSvENj<;n=i~l(?D7JW!*$_l9JJcHvlFu-)#+rwuCslv zN}tuMJG!I2xp^*DA~|}FN}b>uJTx&h(_A$E0H|;`p`oph-of)VH{|8D11OAX%%r;HH7pF@%GFm-UxmOIgLbL`Qfr5xomoPtgp1|}=4TK%ly zvqndt*4@_l;xo%tkKD)C@)+7P}8az-f=1rDT<*^Z3Dxu>`|ZWtrVj z6gKl|u7(}!IreN>5HX!mKh9*V(~0!C&<~dX=M-=0j%)0rZj*!Fz05VX`P?gmqCqBx z`p8K$PT<6j@7K~s?BKlb+oenM9a-riq$d7(@^NR_n}0ftQdZ>K5|>TciCyjJ3~Nqa zCp=lj79+CO1wY23ZvdZlSOceq1jA)Ghd@OAA> zA8A`-9KCq?5@;b+x>?tHnRW`osaL6b<)YcFXx&1USQff{W{6?@pav=KS@^Xxu81#4 z3Gnbn67$C2nnV-i9@+aIl2kB813k_1J1_zpW`41hIsjV?Zo-h7YS8!3-vjM7$XfX* z#h%61emBPSgA4?iJW31RvQL{+;EZd95jTw%8$^hK#Nza$8GKan;L3G1L9c)bwr(A1 zp4Yg12V1ZpLj#{f&KA@^;aJSO)rLgBv((19M~RYXu7P3lSvX5iJuMLVm^z>NPRQPC zMnW@Fs099geEcShN!FSOx}1dJZ5=Xg2a(*QqYS+2N=qqzkkX6_eN>4!^PN6$d&N)i zL-c%!NndUc-y}>Bkp2ym?8bt$9L7Y9fLc0WC-@s@(8rE4`d&T#I^r7P)r?BaPJnQJ z;%bI@#n=u#;T2}}GPBql+{6n@JH|NrSN*W4%^uZyxDpkCPxdML^9k`Xhyi3hq=5eE z9p5Y{fpY-qd;qM|NA#`s{lY0Ej(qj$+TR zGY?Zp|4toWpFQdYCD;ogiR)|`=m65fo}5C_i}Y$%Xs_aw&zf*krBy6xMnbKULzU2L ze8Ghq)GXC#r_{Z5dzd`ibh){+a$=rQW_`?`pk9kh7E{C*C+1u~5{=%Ac=rvwgNQU0tYwj8km zqq0s2%kiXOD2Cr2JFf7MYZG+|U7KQ6EU6KZq|}hK)x_+uiB=*{M;RShM*YOD>3HcuSw$R1 zdgN{0l0>2c)_Uc9d;2CKD|(SeKY!;-p%zY+$VL&fd5rHb{6z~W;O#4)C6i3kmg-jl z&YS&$k%u?`gcAgGJu?o0DxOS_-Px>={YE`Vy9V623O|YoGmgcR0*~HMWQ5*Gv&g2A zP8a@0)Ig@w2@*tuLL5)cK(N53(2f%X0}6R86-1O+m_qKpC%1TI2$5R^EdfDOP143M z-Z}$PR}?JiLOb1IkFPv(D_SpG=AM%8TvI%+K8~i<2P{xI%jYKm-2DRVQfj6V=REM? z(W<9$ExIB{v@LQ(K*3?5RZ2rtV1h2r_FRIUfn%dB^2eMiEOzm_vk)cVPcJGWy0TNf z*CGmj2*}Zn>7S0P`n2~=_-6on059dHy&!1{6kGo%-{_Xb6pI7L?>@{~6K80tyyU|N zm>NtdendBj(45={Pkn>>C%O*JE|MV~a3zj(oMc9A#!czuRa}C_TQ+xlpdk2^Mu!yN z^6FlPb?)W4IZqzN;Dv!#QXOXzGc}95QfuztUkG*7vgU5a$|+&=-Q#Le2*IU+)8en-R)e%k2LwmqCG!j4Qv1(a}0Tr}j?%SEQkQC}$YcWG`yZk83g$8SE-) zYbZ~Ohfg@evje^{f}*Tm-iHY5ga4TQUAppLMct@R1>)^YnW6o)^+=6dVs|e$wnB#M z?fc_?4Ie1-j--w+XamZAg@q6D!eE6%EmM!kP}74t8>}=T9Z_qVGnw+vpD!~SA40{E z)Z{ISZc5yqlWNCx?N$AuRbqN(uNmX*8IsuA_Rd}j)wEAK4GVH!Kh3Bc6yC-*^7c-||(g z($VLvqi2P8i<7{OVF^}xCstaa zhoomT|bA5olZ9P!H&mNvafza91#^_0Z2P| z&h87_@cqTc77`2neWc0T9IiK~_~IPYk7Gj{`S>=3eBdw8u>35ZD|PwP9AWHsV4PN# z*CJ75jgk_{4fwE9p4W3BR4}B61oCI(Bj4->1dseiwEwJs|F{cLuiymipSub=;}@&f zhS$kax2BpkuT_D7?n55 zQ?#$Nn&I2J)j}cW?bx-dY?_@K*RKVGgD~=`n&l}M2`Wry*SL}9E2z)j z1jWHG&y#;(^8$Y5?Xr;CS+2Orr5~r|?H+ehw%xyi>1*x(O;_gYpG?zxGqb|Av^j$e zGwuu=dj1s%V*Xob9e5ij5DLqfM-o(mjyyp;&C}fkEa`%)s0LGVC6m5`LeV2TMc{NB zSb){Bd|Aa=QT_C|#kwr1SW}U3a@kF$lme!Z=2SbXqmJ+VB~#Un15H;B*K5WBz%HIJ z8_RR{cU$FTtYqAMsq4ZLp%2Fb&nm3~?@t8{cNT&nln0g^TdZNHfn}#j{{n&cI_x}{ zBuJzX>@Ek2%mVD94HTA}@rs_JWDpduEYe?+HpO>k6tnSgYlf5H} ze~|w1bf0^qKG2G`POiY1;*w=2*Am&c2=Hs z-dOTu9fEr8L4)H9B??rmhFRG%A>h>0ME{tu8)@-9dPYi@ zg3KuXT2jXcs+=$H8C#fS9nASLQ#^uAGXudT3x{w!p?UvAi=CN$#m31q4zXdS0m|qL zG*{=GA@}$!rOKWu^x);7&5kD1AWx_T-)_f{OK^Wv%wZrq$?~6iK7AmtHkJZ)cE6#14ue?DQ2*SbA+B;>Ot zmbqw8E+1_gAb5{j=;17C8#ME_M8H1D(@?Rod%x~AY3y2HecGh!9t)oJbtFfe6@q&Mq~)KEBc0lPpQ&6*5jV7#sM?8GDX2h1#R($lRW18A)9+3xyZ)B7Lu z)3m*YM9_Q*{pBfO0@G&t7IoMuTTT;41U}?jzQM%a%7<{tsGO8ze5d4JXm1`gI}qGbpRgFZIA`y6$@(Ky$y;$0@=&(e-)N7C5O zy&KNPm(24?D#RvHg3>c?A(K1!d<6P2NvG~_6RK(LH(mm#wrp{1@MkVTKD!XTY-KMa zenq(q%?%|lGSu98Uv?E@*n{<1ZLrWA8_ZUj$TDmKL1Cm23MQU~+V`nkwSc1C8YEZ6 z5AdOBoVk!6zQNRd3~IA-lnhJ%FicE(Vmy%W{LxGkfG`?UFzB_eQecVMbZ8aQEXCw2 z%CKI?n69R&Hz*wm&xDlT(8^=RMEhk^3$10dz|sVL(_=`Kw4L1j@oZ9QvRT%#-G5%x zg7-W|eA<+3_m^D&3AJ$OX56>4)vehjvFd?rapnbCI}HVX$^mYTg?DU~X+O}M)OR+| z`!)+&rx zWORFzJB1Ld&^F5hh;TdKvvKOFe2Uc*7T68gfXLcPWRTew@D9b!y&P1AWOEp72MXU6 zt_px@>feEO{3QD!01*Rv8o73;R#vBZV&@GgQmSs5u9f4MglU>kwrnw#*P7a+f4of! zg|APt2~T#6Zyr6d3TJ$Zw+98L{Bs5jCd*@1A^_9B>s6#)y=+x2Um3K)q(^}F9d{1& z*lkw4T*10N5rZn*V`fKGy| zj%@9BBMv{Yf>$g^&_{4fg&43P@E2vJVJ|Ob8Ajy(K!T&7(ps_OWKoe?QKZOK02Q+B z)>M!fR;jJ~i9A(E%WjujCLu#E7Kt923vD<$GO#aYsl|)>MNT-w`XmZWvF*8sY46Gh z52y}(4R7Lhfz^|3ztONYj(Ri*Q04?Zw&T00Kv~Ft_;^%>u7s*B@{7(AXV`nKzmUn0 zdVp+0i*n#Vf1E*s>t@K|zvV-Ka3hU`HO*v$uwg9vN@^f06#-3wxuYCxktRLcBgo5$ z-xqgE+=J%>%)BIa$95o2e^hVbC%FUE__LU96%T!yLvjT@ zf@hKo(oTXWG9}D6S_vq&m~0mel5}A`8$MD-5Ee|_KBtt~D3+AbrKmXvZ6i)RJqmTU0z^t>^{J}ve|BQdR9xc271(Lel#^3QJL58i>4*NufWW zlk0+J-aY{-+K9`kpYE*;`kcm6XXg03>BIs4vwI zGnZT%U+QV4X!m4ph!_IstdQ+*bkW~M4QUMG^2IK=q7xd2-if?+Gn4sD$2vx)!Q+Br z-=L#C_L+SzfpV>(dm$L7hpB{fXoDthHp-f4j4^IfH6bo83x<@|r2~v2uQJFlw(#v( zX*$IqQ$D)abyS!%M^>FnHjCCCsSsRNjAEPaqxLyJWvqfTb{bmn#cOu1S_KtfG#6%x z6R+{n<(4=z_euY1zxzy+dV4fP*ddkId6^wM-)VaIi@RnHL!hR61*NBIci zNdhuGhi(qr8ORg1 zs%61sI1s98q+6a!apms1O)f_GT2sq|#!;3PiL&f+1B+crV(m)(IuI1&PhA%P-lM$Z zNI$>BdSUKutoRZ9*(0?(Sv0N^;5x*Yd3bj&_VlHh!ktMyhEF@LaYoHwSt)#IXLxSA z8?akQcaztb+R_`9iTMiU$*cuGk^No4@im-WE~A9KW2B%D>prt7J2!`Iaq`Zkb^Dcf zP`uSE&-*j{n!#A#@~8GqW_S_scqo-F4#shX?g=TTqwSjh5Gl73b|A+Dk9kB&UEFcJiB?K&T}Mc| zqjHq)qDAm!sA6c3%2r*A|KV@dUD*V4h*Rm3mu#i;UvCW9X=o3XQH$8HjBVdBXPjGY zrfvH+ji)Ha(=16p9HYzehXD$owB^HhGrnNXt7Y>UL&H%tF#-BJL?$A`;*O|IVQtWt zp>WxAO)y8WKY{=Um=#31AagorH$Pt|sogk(_B2#%C4mFyZO)f>BZ80!J`#_*h#GGSR&RFh-f@R)vBoeqHA? zN1W)st6VT4Y*SsYDz$(DbK6D2hS+|5AJV)+T6T%*`4Q>8xV%BvjPbsWM(Bz?s)OIs z15C-^p}NTx3tqxJAaM|EY=q3I^;!#-a0=|*m$+8ky5L)6^Z>!R#vZt{AThcfKma>C zQ}EHoF`Lp=4aai!{k6hI8&~nh)8(cZ$HH%?=UEt92 z6^>QgTii=|pXP+eoSefjoz*lpoL#>kKccI7LtwRaV>h=2u<|H#5VNL`BM-&R2#7oZ z3D>>6BAVNG6b|q9&xIb%p1zZIUVSj<%#(K0TdqNR=o9czeNfEnqS4~gU!Nc$HG!2_ z8z>sbs|p22rn<`?mGv@_y3LfFPcLBE#u_kB zIGbo#`kNms<+t`+qB<2I$?O|^7$eGvk4XgAPA8-7x=RYc>>cdWADO<}3U6-?C=70A z1Neg6uP>N9B;Qb`#cuQQZ@y=aR+JA|Olf(cO);FJuB*;w*pQ!(wJW*kh7jWIN*}BMAhjhge3NPHRb7M_mjpZn&N;PWe}S zh@xTkH8?t5SRVowT@aD8HDqy8?(8y$xB?uyxghMX7l{LZ7ZT7gZ_Sxypd-GNDSSw90}=0ie73OcCRJU)pmcbeng<$<-0^ z1DR&Fu*Z&}K~9ifL=@Y;$PN}INMh|tDR(d8NDib^GaA4n4{mp1sOP}2#XC83yVYie z?am7-k@i=5gvwU53g5wW3CvQl(tn))NMi89?%G$xRG9DWRFuT{`G16+QPa+P{Izn@40%=S8GD?Lo%#6p)r1KxGi14f=I1%Tp=qgZ=HV zrM>~VzaKl%u~LQv!KFncT#%qd1EAp%NT4LRT+uqSj9?j5KvJ!NPyCzpxTAz0Gu$LO z5bqV7s%&|_%}rxziP0rZl1=Fdx+x{FlAWS2 z%U9AbVxKA}#lbuQ216=R?iV2nyLdHVY9z$Qm7NVIiE?Ii>1qvN88@^~4v_3uDobm~dr&6N~q_MPx$%_U;kZl zx^Hs7ghUG&8=zpb1KeYY&WJ+LPmbu(`GHpl3*Q&i`m+`J zr$$TiH^Jv;dGJ;2L+gI&kuP5|SXSRR-+#LQmgCCRb1(q_csc*Or^Ni9r^MR8?*Dm8 zw0;wbM^$n$B?{@OEt)m2YhEU=r@imjv2R|y%#5NOvvAF>_egqhdLnyaT9P%M#kn;l z3NJCQt5=A{9U$!!xEu-N0Fosw{3Vks2~ZFS- z;#E66W3q`j-osT_rbN%P(vf@LSBf+cy3`#jR9GU3 zaBXEO+;XkVsqsd{(MTHy}?tw`PS(HgFntxq-Tei9T;24+DTUdbA?E{^P{J zYgr$}^w`g`nEoRXUCwHboFuN*^yg@V*e0v3Q`1-UiL$lwz?Cp`+D}zn5TI`7GN9@HS`kF$>AOXuq;dVUfYVQ59~H!Dw%n*=5<<^UtlS z_n^gUR?Fe$NJ`c+SnDO+&OxvNCi*JOiMq2*Gt4flu>>IzRdWVndnKCWTF*3ZJTLnP zc|)8PkD+j-sNLDF(zWa=zGNyB?THgFN;Rv_B;VgQTzqTwX@z9g(Z_0+Wip+c>$!zT zn7>QGJKgbYvui%7t?*?`HOyG%f^qvRFuj`=8#`%|Q{@|Y-RlkQ^a~hZn-w;B4=IQ1 zR%_AUfC#)eDLzv3x6V2Ra*y751>i7V0?)+qH}dKYfO@7OQm5NY%L*X4Dxp2iI>B{}uBPPlN_cjF@1S-JyF!`0TKor)^N zgC(%hSiHu^K*z;idcoz2Y{;{QL_2dq-AlsX)V0K@mX)ue7ie<+gk#vLtDUlm*?MLe zRm&+V&fV9J=r?-b=Z>53e9G=;{f{o)J~^Dtn#CJUJhSDTY<#^2T0WB(Zk5?P*ymEt zo&Tr^Z^u$aVCN`mE;AxZZA7OQ#l+SPYX8QQo^`uo`H8MJ#uD#L0cVyKNd>H2DHm$+ z&%Ilok8CHRSg!Is`8?=T>=gGb3}%9~{4Ggj`L=*9WO%mufit@3l2&mu`B z%M$&jnw|;}26*{q58PqK{-ep}%k+d@#})PXxLM}sMm0@vN0zX`z-AqnOPc$NrOP!r zhpT%U78Hc@E-CDw+hogzY1N_YfcoSHoDsL|=#)d~a=8)7=+xv^ndL~Mx9Mt?%_6)r zC+A?(_DzdbjD(G;NOOd?ngNrhp23tQn^5&O<1-nWP$S;RCY^0CWTayy`Ur374xG`{ z^Pu{oO{T;oGJV{h%34HZQkf^+>);ajRnFP;gcZhMDTA|x?Gz+dzJa61vdv&jIwp%2 zBJQ1FwIkrHh|*?<->G3*Rfmp(cxtm-{D@*|@$^(roBXp-vvr9k9C9}FB8*g(r0TTv zDh-_&)ly5Be04_0at(W-ZBTg%zE6eSel@wQf6pE>i_{5}rDS9!CT(rWk{3-4mZgRc zP-zuXYnh2MZ3w%j!1P=$T5Rf4faZ$nGH7#{X0f`)bOroDD})c@$gIL9=Hz9w{)YQ4 zkX&4I-g%qY;{C_vhHeT6%lyE=H#!wMUJXzEc76Ne07g%IwQA?v!wyxWS7k;t*<&{% z9TKz=c7vA*fHDgJ*aqosI;GTp3YTYCng{8l(MDqxKN{Tv|FP9 zW!m=ZZfcUgX_A~G9)=}anc2)NFmtiLvtnzr&pJJbW2-Vj#rqGCmt%Z>^iKibHU+C` zz<6FClhuJsRWC>sGd4oDk;W1?m~l(gDF>d>=j=CABzR-|pYaBwyItF7xu*bq!Zi4p zS!#^V#2+sPY#OO-2m}61r5`~%q~6lN#lYb`l#L$pHgNOH%Q=T}KC~<^8A!B1(x}6S zu0Cg)(FeT+UE(dbEK+~E7$Kr>y4J@2`|mABNl#2SV@x;UBF5p={SQ%M@c)dS&+<5Tp=CYkuEEpND;R)%f;vD zv0jMX>1wc)@=07sfEFCn&V@zQ?UKoHPRF;280fya8ff#N4V}6jM1l*u&J!y>Lx6z% zk1<9)R4c{26Bg@28Ge9*RqH}vP;WN}&*mO3%L<3*0n6phh$74wGe5#05z-izyP_j} zlnzUbRVz=u3Q+3JN1TU7q|K%*7m#s04?B{Jz#ExNBP*}YYd~v^ONMZ1f=qK9VttTZ zyQYUwgRfly6Z1p1PGeg9{DH!Y*{C-b)yot!Y}v_*&9m{)jp>~EmTQQYzEagr88`Ix zAbyvIGONDsb6h0Y8G-zq`R5`Ae+?M?+>mJK{wNNTqwcwyWuH?AA_U03GtDc+vJ19- zZLx!zM>^xBsGLn!8%^t6GF((Vu-WmD%A6{iW1oersdGc;ZdjrNrKsaurkcnDw+GU42 z#Y9EEcH_nxQq`K9cRxhfq4cN40Qdq7V&kXzA&#+Ke# z+HuJ)TjZh`gv{XsgVzDokKTFpYcc+i=n-8bbF;bf%KGH|E#Srtki+1BsdJjwY&Iz& zs1_UqVOg;N8irs-Evb#;lEFIoVx<9#3(ijg zhrM*v2m~Akm?F+FzYjD9FsVWUA{E}ULW?)jnkh>i^~&#_Kt;y;7rLb!4#5?9VoXBo z9#6d;U0+F^RvM5~ZGdAPvj8A5{W}?d#3J4hN%ST zY|<9F#GmpK;{q#0a-)x7y{Xme2|Kq%kaiwEBSL8k*^*l~?P)vWjbMWfgEv-Ak-Bs! ztq|VJzX9pI*@I#|K2gaQF-hbUvGn~529+oZxgsV#$}fX5{uz{6APPFI2iwJ&S=vw` zKA#WePml)FRI+)B4T7vD`H@ov072?NV6PhMPgFO4~27e+^^6Q@n$Q6es!yTCWtRVGUy7({+ zTGoMYpMY;qz66b+!f4#JNQGA@fXJuwt4SC%an1}p7-ZUD+zJ^eKo=D(Lrg{(Bn=p- zAHo%UV#2rS^&77+M8~5UCU_VoyclL5tQ7T=SQh;hly~mXu_F1cF;xNnq@!b)Na`mM zF-$Z)$8K`{+6u4c$9I>zi*B@J(7F)*rf27g~LYD zJ}MDDp<%o!be1+iEs}w-6%i|uGZD5Ri=(%ol3`byP?3OfqIjZsqBBYWy|G zyNrBFz;7nckW`==|Me9^gBT6`uY=Jg=kP3yLzD}%Vf^nH%mYzv^g|*IIWPQV?0cd_ z`i&eY=UW}6s$VUEEm9~s=OH@w;!&bHqOLJi>`X~8Ic0z1l=^Q`jAue;M+`n>>7CKv z4`7W&=}DtO4r=FYt>x(nrN2adNNf)hK~naJMx^cA7Md#DOl6;FY|8AT0n|UtL%lpq zwXad>tH6mTdEVK1uC{+Qq(W&c>tdLNg_{yBiJs2*B@DQQaQ7h{wIIycw85w1F<}o` z%6Gz#X(T7z=ali-Vu+IFx^K7&uzu|0;{k=!Zq%DRWi}hwLrM^$12HQ$^EWyxHL{RQ z?Y-)pl4;P^>k{}cEXhPS8${Mb1+PfIrY(iE()?+cRDLEYzDFLww>#=jmMnFkh8ah% zG^fkC*iL|PL%J2({#X@V*_c1IA! z`VYZhqw*N9!do46J=WvmB*un+^qJ@rH4_m?QYHDIT9xadzj-0)3ngd&Y0o#p!goU+ zfU>SrkMG_Buf;M)>H`-6ye-ExMls)R<|RwnFW5Mg#WCGg|B^NEeSiT;D^X4f~S5nZC!nx-4d- z!~~ZwGR=@t-LQDmiOe^OBk)`waUpd+6Dc$QFZh5B{v|$q$_E%VIN0?`fTa@@%(ff> z?=0G%a!8x3X(dZpgT~0F)=bMO5c0r7D@G3gv*IWwkpipp7Ce+a;JBWmyd|qTn%zdD zB|HvC|B00KdkQtUrur=&<~7Kt0}}V>Jn7wXJvh@F^E=w|{(%}Mbz`jT@<>}vpkId} zy@6LgviG>gsWYoj6p8M8RIkm&_*E{{Db^V9(zAfzvu_Zo2=9&AH_=03JE-`L?lis9 zyQ4Ml0mtb<$7=Q2U%l3i|tZRj&u7H^}__MSyZJu0!XX&En>8hqHrBU)Y~gulJoo$s4xB@ zXDT9Ae9@Y0+CHhc(uHPWh9o63)iWdNo~RMiZz;=NpDX0;NFO(sExwB_nD_xI& z8S;1X=M>s5+$rcbK$5k*{BY%#Vb&D60&LlWDJFJWcgG^)_H5UBp4pfPtz?Fh zt&A@!KTCE7FJGHHf54uHZ9HBy<`eO`eBuO>A!;G`f;1;RA3kP^nxl^w_H9ZPultdq z{OSs>=TnYyYNCi(n8AwQ{;d@w2c!~WJb&*`GHq|d1BDP_y#?Z3p;q=!r#U1}6ou15 zE?aH6rgky>E9igVf9OXBgJ3BYhKeizIMUZ^rEQdsua0 zjV{sH6IWi)qBY8-n2|0PB-4-qXfl&y%x9vOD^t=7wlz0Wk}2zDGPhDHXO}Wvwrv^* z`_7v{WPQbtVuM2JXbl9{BIHvqIi*FV)~4{aV@`smCR5AzC!mlG8F51NauEkbM~A-P zg^><{*VEpO*W6>l>;x%v{ZC@(nR(OW?b{&~Zc!WOTyf9L;@AMYDd~)nITN(dWdVnmS z&4?O8EGHEL*3kj!d!qo5i?m72(L(Ac2*MH6#1{bDB+1(Hot2(f16Y}Tr1RU(F(VqY z75=pdys9CZ1V1NmlxV;A0jX^l)dd-Y$c)bcp^;v6=66{0v61%^RspH!^ea%$8+R%c zUj+m_G`7hw3FWMps0f=r3KxYW&~FpLG6LoA_9_(c=datt5()INEjm*r_So(`m*9ql4+^?lN6uMSHiTQ7193GICv2yyR<=pR^xtf>P@zzPy z%^us)2*U*Rjht9Z*-#q^-Q@E@L( zUgpc8;z<_Ks$90O2~MfH=s#K4D|Uuutw% z{tWp8%eOto=1>H_p1N7@16cehkk2?i&nw3evIir3h=FQO+0W2lqj!p6TD6 ziLh!%oQ)uxte@ZDbPEQJ!E6z31hJ(U zPa4CTLh)sMyvgJWV_=$O1U-1WL*F>8;E_Wcy#U>BY=oJ=&lqGrIdD*lxRCGdl7(6L zCCI=&1ug;NriTWo%PC%u{MK>I$@POIP_j2G_Vq6G6&%)EH(TlxF*#LQ_2!=!S9cO0 zHYZpF+-I<4ooZ9ZsWQ0H@csiE@>@&x>q{q&g3cBL)ec8%fY-iV^8y38jqt#OCu02? z3GcoidQh`-VaZV<9&FR}M-ZZ5k$Gr2B>wI-nhYVClVR4w;X>oj#|nzuK-{XHnwUc^ z@BPzec=6xsse?4`EPSqw??15O;8~udU$ zdj|LjaW72Xh5qzMb@r`NP%)*qESF@K$~Lp#47(a;p3a10*pRz#UtqnuQ+qys!g-%g zEwUJ{+BnfhxUAyHT?s6LDjfDK(zkS!YL)8sxOte6(|hiV%=nl=x!2pwS6r>j3N$ce z33c7431bp*6><~WmYGo|7`KGMFr@6V8*jS46SpB;XgQs)GbgHdI6-^B@P)lSr-pf5 z5M-s{_lL*A-Up@+naAHe*vSjKL3$bE1E!m`4q$Ba`%*_4MEqs`Y2gE7LxT^D<|BL< zm26V6y?6kc+GcBcFAewd;t)kjg}A$4C;T^5i0v|(BRDO%BNRbWu&Y7oQKt3$)TUG& zEe`fc9(xP{l#n2YP3gdabIlp39}T_&cfm0dsDpy$ zNXzYiR4KjHuYS0HB8h$#MEVA}hu(4G^#~u_iFzfER>Qmuk_t5XeZS?YA%E18entpg zY^okk#R_^Qj%?x1A6kiZ-gd*?ZA~>(6}36U(i2gw&dkx~cH)!O{acV| zVqZjL_}4#f_zN?$|6jQ<6@VqtAyW@7n&cn{TDGFCWZE;l#l@5gsy zB*XX`GcHXn#q07y5-%GKG=ELBQiz30Kowfp6EPa6%kdh|9IQk~wkU*fa zL=Zs45#q*3|CJzSIY`AB!Tp1r5w~VJK{~t;j=xrPczT2ssYV7BmY3J$s@3jzKL1oa z{~Q^M^zU-p?0$v1q8#%jP3ZydZoYl|1_V&T2A4}aE`?C}G>4G+I7Vl^En|1QO|w6| zO>+jz)+;CiFdx|Coo+HXo9M!jWUPqCf|XWmS?&allyc;Gz;j{seO zs(xu&&@_;;^;hUsMw7Nok*%XxPF~?;nlhJV8=0|LtYCVXZi2Fnp3HwBd4|*0i7d@9 zTkDi(v;QiqB#GsmzKQN(>O{8k?l5+c!C$2qVFI#iakq3@T&?$~+=u0Gnd)P{a-Ebs z*Uvk9WVUG4D&twN_srj`!#i5F9zUfWV_)ZNLRrmLBa`u;t}J@g-b z|B0wqW!Gh1(b8&}q#1RY*I=*eW618XKi^2Mv|Uk+f<7-`_cIt~fT{i44x~|uuU=zo zs~@Q|3d18nxq(S|rJfp#SnXpl{5AyD!gGSHR{L`YCeZ0~{IIE}8U>!a)oG;dd0Aaq ztJm(c+;-GHB9;SA5R`qI))n&2OT4dDS*}oi?99)*E7lFwMVrVj@OQG`ac1|rsUbmf z7hYVG58MdYM2k1b3$GklV>TFSF{Y_0XL9M8*qWK$FROlFRkCbHA*T$OcCPiYsx9#l zTbY}tZXy1{7DPnVr{G80`8QI9p|?qCPY=|D?Os^LA{(iahWb)c9eus0xja+` z&cT~_>4rj&4)#g@)EBqHz(xb}Xb>mULkjZM;FmjXttLU**0S$@ zN(q))ev&40vMK5Y{Bl=$t5YbdSLHn$W9EyI!%WVv5Pl!v6STf|oO}&gYp7g<>unXj zrD`R{cFp>*rrR1j&gbO1J;sbF(%*Hji4n)J0|9RnUcAng_U2F;V@HBHIP2P4z?6pW zU!w+WUHUPY%Vr5+4+qvy5579C(dto)1d#jwO?T`q>uztWYOlW61=lB7cob$_9TdmG z+fM7Yt{%;O_wPh={$ z>Ez=lcx}&G-LRAT)(rxkZO^=-(fsn=^kJuLL1&Pm5@a&H8CV6%J_6C#=!TKE&U>)p zq)5i87}rZHDk_M(BZ=}^>ZuzYi;kk*JHHG zUq{E}^=p^LRqvJ|JcI(y7&s`N8t~4>@E*0`D7W)=BT^k!sfkv6m_H#Gd#e3W3 zP~t)$=J%XyQTO`ZK*!4jxxM^LP)6W9M*ad(N`v+O^deJ+%IL%C3vg!q2y4ugL#;S) z8+)Q9W-6=?Jb8|`9#jyW-;oPTB3>U}(VgZ16+t^x$%zLO-V>qkzMF&v^Xq!ip3{SK zeUGZoy=y&KyD4!-=M(kGr|f*IM2M>861R8`WkE!hDj)QNwMSaK^G&?7lt5)sQpMUL zox%uw{_+o!ZiQpV#d8gOhnxE{^Y%`*SN>DF)@_1`^qW6muWTJ#fxLHxIJfNzoX0BF z2&BEhPq1Z{SQP&4J)>7< z1PWmd&g?m%3Y*GdCcgO~q4GaKT=ph+@s&UL1{2A0T)>gl1^A(zqKmaZ=zX4W1$X>1(sS z@_4?mm+~P$v>AG*D93>7TIYHYfWe1==p(mjF-G@mlVQzTrezj=T?>2rDB=q_wokB~ zA~y0@IsAN|FMy(VYijF4jPo1LoLZAQ)0Ilv~@E}jA~nI%S4tx;^R zT+VKayW z;v}&`iCQn+c(%huG5gR+xnTI&-7%lO+5?3W>@ai4E(%v3ZA;yRpd@Bq=5~HW?fIbH zQvq`{b4yFWg&1+yvf@r$Uvi%PYe3{yq$g)Wby=N-S19m9;+pi&3QD2P!OKV=!DOXx*0vxxyex+1h? z=u6NWpemsexq{pBvjX?CmS4_Ktxf8Qi5>K08DHR%qKSX~!SIpkU<;U&Sy+;bQ0G&j z9p`K_&I4-j(_|~jrK{)b_XIa;!uSHbk^EYw2_dZ&p^U^ngHb~4bn5ESRWTG_jy(!T zUu(>nf#0fK{!(L$Agz=_cPeY|$vL*wV~b+X3?RIakG^<&70#l&4j8ocK?3~dOmz+nJ&(lUl&M#2_`SLIDcTaeU@YdQpP>V+W zt_vIgpds;(1Ed6&Pb-bhN|vQ+1PAy)z5W_P$@4(hLtry}9HQs^bV`K{X#!LSg zoQDZFq9nCaTjgYXSHMl2QQFE8Ff4#2*Ev8qcJpFV^`qQrXp8PqSV~z~b5>_d{iQE% zepLTFemY-`;hip$+m-q&s`>fcu%cUx<#~M$(~jKd{vj?P?!h=UFFW>* zAq~Qu4`VQEH5-CVak@sT>X{L!q&Q@cd(+av>+6qyuE$QPOw| zOoIcz(rc-jtiE|_bs^|&BDllsg<4`97nRb*U&T}`^MFV>=0?x4- zmK`L+9zBbN(aFO|pdPpQf2q7q3bliGC99g!sRX;QnY|y`tO4ci8(__SopPNKy zyjZU$g@7sw9FwURyQ~mu7>h9%vF|K_njs7IfN0vJR5vGfVh2|ou&^X|fCx1T3MdP; zPd)`RwM<~3%bXsQ_DGII3x*iqb8K#h^~sg0g(tPd7`C|8cQ)@GOBDWW)L;LhT8SR! z1vsA%=SnfNRKMo^Oqg zOu!{`8+U1Ya&ABHLBLCl-%hCQT0QV@JHo3kYSe`HAKYZ^@zE8Z>gh>pLZb zwJ?DXTqtQ!d5**WZiu2Yw!>3DWDE~B|%n+qSsG#y;nf@Tu z3k~$C<80zi9LHW#r}%!9cpCy%_)y9Eu*J*QubwcMVtl$0MR6ZQvyuW4%TN+o9QNa7 zjM09Hzh+kyCkcmiBnCww;(e6cVY9Fwr9UC?hNHXFVRlRYuilF}gnf*BoJj zgFDOZ+6`lPOo}XWy}3is;=lP%E=*7^#M49WjCEpC1cx52UbvTz6h7 zXk+fZGWViE+yS)@_`emLr{1@otlajG-0}A>|TUR2TjBT%Hcd`Bvw+G0}FAw#{caK z5s%mA;k$FGM2P|GSi}?(2$E8{;aE>%|MhpQL`;(&8NMIxAVJJpx>xReOx&P5k28v4W-me*3h>)@8Zq;zO^VB0e4nW5T#k83AG6*hti#^R`p;u=}nmle~MpP zhMDRjYGl*z>nv|WFbHqWC)>C|C0y>xkWJxZ7GXpFly6-bEZK*ce+%U#XO4AI=k3YK zlRz=9zBLD0rRiK6_91D9XCcKzF8&w+eHt9J1Rsr(P~sW|mM7;|(EgbLez$k*fxFmN zC2m5vL&PVxPogxgV216KXaHbjT&ymPKPCh+iF+Jbh!uh6;oEJBv}1$MmF#(ds}P zxv*jumlZQMqd)-!xiih{%QFnKGH`uwYX2%8X$p5H@}Y_XUAUcMf>ePn@?k5!K?7^b zAOA5-@{~XIF|cuey*8kxl(G9o~7S zTBhTTM5Wi@g8@5-duj_R*jx(wN(&C|m0|3xWM8W4eHhl+fmjCb9pX;bONQDZ;vk^f z#Ho@$0K^`pW`1$=&T-_j2E(x4A;t2){qD?N##(htMv3vt%duCe1>z)*V`xRsl z^Y-Q&gdCzp+)dQ@zhe?WZzyF4YM8-E3LgQHv~s9f8>iA#od7tZ!#*`mFQ0#TEdYH! zN+~4l2p0pAkU>WtL_RpmM7yFM9c}0n5#QTIE@7ibTWR~2lm@kfj(#)nGKdzNr=!_{pnyh2^q+X8#gC^mF5<@^eav7HB2ttdba-a7D zPZ$d=d;@@?!~#r(XdU)Jx%1OGH9afyb97!>!=eT6)heP5*}%0*$DxK)D)b6P_jmh{>N7Td*f@T58kjfH5Qq}g@gqCsNDd8Y?#Or&;rrs_J5M`ia(oVcFNi%68fLJ4mgJ|d98BNK zxWJ@ct{Z#BO_UUgc(YLyzMcyyqLHBhuapQk+J>;V^>|SvA4o7-zVQ&lYJw!khy@Pr z9-(X7{2(G^#^7Yn6=iDnXpEbKdy4yP&GtmRlE}lvgt&$Wh>{L33jh+*fw=U!6~{QN-iv;*o#DeE9M*tbocLce@>j|%oBo2V_EUB; zBn7LFS~DA+9Ry$mFOg)z!8SV0RX*P_%DI?|OZ;tjisF&O`J>O1o?~J`AM-{TYO3AP zC%0Fb8?nS&Ug;5qFwYynV+GghS2mNy8deYoFwtF?66p;7t(&{nGmTr|SaZWPrkBFgy$C*%u8HT$M!?c@uC}@a>$#7DH+xaTSB=2vwL$sSiF(bdY zh{T%UCaf>-0T#QtLC~)zA=H{{8SviTSwx-#8=j2 zfobCb(6ot`yXqc>v~;=*wX#p9JdAo|MGCOhvkg30I)o?^vcpwaWC*!7=mF;&WY zx=I8dS0P{i%2&iSl7clu!7_0P_M}eN!}i_|_QP!u*rIGz>qaJ^Y!%W;{yRlPV+aHkl{^MMTb2mBj)PjkN>9 z)x#DV3M9_+oyit;>BV_!8qkr` zWn`6=$llFoAEJkk6U3`9-?ZiRkT2XMs3D(qIIn?8XLAuyoh9HDymBMd%2bl2$EvFl z)n1fpx^Pf4WlowiWZpwgy)rtzgooh>wL_lw0l#g5Yam?#5g~LGCtT_IP|HOSJ0Ta? zsD5I)2%Eaa8+iqe%{{I(llb!T9gY4$-Y~b2hhm-m?(l+`2_2dbta0yug2$L8AD>8y zZ!wCc*iGQh(0JdU1nX+%BXb{wYJ(@VOWg!m)I6jVsMV-X1qhCx1x8^ns!ysS=_N6)`W{zt6jvY{u9W;w-;Vhq*5+7G zR_5$DX$!U5xdh{P5Y<#MY`;>zwp%l2>CkiSz45tSh21GrMgjc!nZv#O?I4HvrJMRb z-v5O2`u>Z6Ab>oE6)*8ZFDyesn?Md4CSjaSQ3O&Cvrd4j7@Q`}CXrahPa~!J8vK&- zQ$qvMDprH2o^x60U;0s)V+3l+?|iuC0Ke?NYIJ^!cU8&$)W@NCtJNRxvG^i-wDt@n z((Y;ePW5$JVGCBVsumrO?-sKmH~hj|5#!~k%G8%Y<6fq;-6FHe#T z>bMf}iN|}hx#QhixWmgN5SQOsVe^@@!K}cb;A~!a4r9&Bpi)`OkM+F#*@3OU=t(?D zW@%@vsr-D?3GF;Hxx+r)?t?gs!C8WRwg#>Ozf?$o@v-XST5f_Puqbhg(7}A+Ok=w$ zE_qFtbb$jJQ?1XA8!I@d9me$qSuw5b$>l`0vLUULC*^Cd)$dZ_Z>{25pV)K$WYu{2 z7E~E|EE?w`f1TVVugx(aT z$%I?e(}X=&)r6PB?Nd<1lrY*QH2$P$Jqv8|o?`?iwnntc74p)*&|X4u5!`DR%Ia-q zM()+WI4KQp?HpR}2)=moa7{yjl6%uttsC-Yv!z>8|3beEa!iSVxIdaeiUQ&fijm3y z=N}&%t;)=%%|37UO^!tuFW$m1-dM>&Syy>vOePY~mzaGej2O(oH_eMkMw*a*BD5dQ zK^aOEzZz86MJ7#UKpCwj!8TJb>|kGoh6Wy7homYLeuQtruW6u30_xU)Lj?@Fz?_d` zI*3F0XF~Sku)D5@+{?pz!Ox+%4smtK&sOmdA8lwfc>x6oY5_m3MGX0YkT~!ACU8z^ zlzSM6X-iQr9M;BZ`pisbaLInS%`9E_~Yr87DYY(VDBsI_TiviMoMs9eD0Am3iOt0u`Q;A z4lWzKm!G@&uSXZ_nr~cdSYLb-Cz3gii#>yO96zi#_@c5#l*GmI!Sq_Fdqd~E$SN7s z-iOvYpO{VzQwgYkopGX$z=rmR3@~&qB)E#o!ec>88o=9cFmBGO1U<%~v+{hti|p@@ z+4tr(g|572VD&w*t^-Vkzq2*N(WW8gsp22ml`iD!MgIGBgEIZmrr=ryOWVZH;HX~) zjG<@)*=`216BjG7kGgh#GJ5jMYgDpLnqcOU>~|JmB%=n<8Et%~%=j0}ZU;}4)qmzg zcAZ5@JJo3eJ#}bFYRllrrxhjr!=RG|7k{K@J*l}Os51zfj5S`CB|dVKt+W2Pl{RdO zTKOrq13>|~2BR`Cg61)PvKlDSi6xPPZ{n%DrxT4ebn)650vm}?igbPU`b`nofALPf zr76;WE)GA5?TodkTnuI&h1+y_tF^X=Z ztf5Y$5c8KfK>;{E3My|e2}E-Upgbdmp^P6vVugTe6Az=L$PJKtmPI4txB)xZ%mQx37uq7Xd-~3?Rg8x3jH6@!{VS1GM338 zghmg2pqWAHf_7isgGLd30qb(^*O?zsF;u;g^odluJEoYOe+4QYqNIvvB4Fr2pILgN zYd}A+)h)8BL3QXHhY>@$>iic(-ab{x4QMY69Xg0nkVi#>PTlS#vOmDRRHQ(kByVQ^ z^Jm%Z??Cr?)q`WRP9G#oZ`){~wBz1P6znJrnJpZv59O5p0Gj-vNLqjN$0J5ypvVN~ zFZYNa?PO}&JriRfc8kq#K@j(vW)N(S2MoME(xm`$rcaQ9dz$~hHr&h$FJr)m(x-UqmQ$y#FpGI2k50 z$P=&~W=u>nBrdr>ucx`bKPWx9ts)slH5o5ynY@#K(ysobnPcPU4R)FJc#e%=8sb0v zTd!y>MKFA|t-pk&#D;!QzHtdm<{(Yp=h_H%WrstJx5tC|`06`*reGf%(M}aAF096U z04aL}BZ?-y_IY0RE07qC`3Y&g#9Awr+JL9BfNsAvMH48>S{NVm@b{+y_n5pN1G)K| zL@;4A&G1QJAexiK%u}s21PQj~t#cwgUv!NRu_3_s z27QlURDF=psH9oBCvGNKNkWk@cmj1Vu4p4QC`X$^|2B$yc&fd-BgST4{fBbG|4ord zr^d$jIa(Nu{o2|WGl*%AaGWaU=V4aC5KBG)4nw+AOgBDUf*K#Lw@k}S5yyiU87LOO z94i2j6LuquKOnYH<6Z1=~y@QMwnJb!@oVt$I5akhwR?R!^XY&}iA>C^L*`#I$q7D>b zg@|Y_SCHz5oysW{f<5NV?=0@~S7uLLx;=(co_r|tQjoem&`023#mCTlMqDjJ8L-9# zfAE6w;&-i`;~c++d?F0%_qJ4(MPOA8v;gzg(4`P!Sm!`awJ!QrtgZ{MwoMykqghR; z_(ngJ25@v?52>-%990Zc)ZO?sFiky{s$EaNcvWz+8x2)d+`BkRX|)|PiFL46Tx-Tj z^@w|=qkH4xGY9xc+Lk@|>3nG_8Ff+cBXK%vUaYhEC^gL~b=sr-$q_hsRyX@ouRp05 zGq0p7Jsv5u{0WH7vsY$M*G3W7s*Z!wHa% z&psqp=>$&UgpJmM1AAIZ6He|Q&3!To2(p}7lOB_t1>B%eognafq23VoO?992dunn0 z-!J2sz{)p!5}$yzNiAGd3_{o4P3})D;3;G4!zbeukW+2~m_3rrGyo=q;#wnGnAlBh z$5C}k-UYrs0RM0wU|=s!X%)zOx7G_}M=CiE|;pKZ3R7C}pVmIW~L$ zia4lC*eEAH#6IRg&Xz`_-kNZX1yG5AEuSM*BWeiHdn8xo@1h_uU%P$pd1Bb{DZ_Wv zjk|Y8_NX!T%q>W*loo<_`;Snl3TdKHd5iUigS0TVOtZ}0h%rPb%Ho@~$VnzR$#_}v zW4|^)7q&dX$W+l}fq(EQZ)W2!*`SyelTduH$SK=#(U|XXVrPIY$J& z*?|DvL}5sOGN`AJ?-3cutiQV3R8im=Ac{tEcSFUNtS#o+_C=Xl-nLp0cLzsnP9@SGEI-sQS~ zgFDg>9jM8D9Z~X1AB2jH6vWDCljroi^?+(kE!D@Nzf}!;x8i^P4`JsJoe8jQ;h7iP&NI`v0CMxR)ah(&JGfqF<6^Gpjet_QmmRLR_O|{rSKny+MTt|95{UVSeWBe1JSiG?SP+M+~H+DZ&x~v6^E1_Vn>%l z=X1mX_wQ<1eJIff%Zxs@IEM1bn&D`}q8gHOPmi;T+!d+=-%FhAii7=AL24@j{UGX2 zDu}RqMu*4z^A_jp`UX3kLHapJT6xxalmiClN_Rdr0zjIe-jDf%Rf%pd6R<8YuTt!lx@`P7!avu*+u|+rj6-EEg&KFoJiN4Z zJ=;ig@JRnhb3o&1_7MjK&%Ql3Vne=7Tz?drM8;D!;N+uqTkaXCP0HNPyq%!Ua4W9i zdJOZAC7A0OyC*TnJ8W}vqMdsDHH0US$6AO1lUAE1Fza!!0mH%BP2G}@sp3p2z5%z9 z7x;}no@Q*!P?1pel0zFDRE}X2@E#Tg!#zsCwFy0q4F6H) zkdo?NR!}yJDu`eN9+MS?68FeSK(GL~3SeQvq9VOS@=|bT(EVHE$o9dIS}4{x`cCeb zcXfrqqnz1Hfp@DEcj3kn#8J70DQkSQwJfO9=)?+cT(tFr4GsX`ES#E(E!mDD7C`Ao z%54Aijqv{;V!|W!m*Gf&fUb=HH&e>S$@srKhF3m1@i;3kx^8lEGtx6N)3m=kVL*a2 zQHk}-j*x*Q;)DDB5MgnV0w9L~VS!}bPF}8FZY5^+oe=3@UXqdG<0M&i@sf(F%F;`O zDQ#7s>=B8M$Qyry3Kh@#C6E55I3M*_D2s$B<2@pt1Ryv}bG*39&0JEu_9yD3?(0~; zTsh5r{c!AXyg<|@Pn3Z0?R+n%eo^hnC;!wG`Ocn@ygocs6wnvf(5G0~G}p{)Rhpev zuQWcgo^yDfaD{jZpmmv+s?#NMEzr7*KQ$DoaWXwow!7HRESr(|)Hvn&`fsj1d!Ok0 z0B_DT;7s`CEu*hFO&W{X)6^qV`Qu6`w3ugUZ3JM*GC{g~$x z8QscK*}1!KubVbBNUnM<-~_~&qOQAS!M}*Rcj+i6bTH6s)+yOk)U^!f^X&G8(v=dl z3xq@&^vP!`7lyf{r`A^nE+qzbftAG-*9*!xCQV$NTB+OFbO`faB+E^nM zvvnySPY_2MztCjRx{QuOXfO*V`bt~h`tZ(9rn3^{st}jqhj>vPeE#!G0a|*LT?_Go zJOq{If+w^qkWz5y|0c-ok0Vm%JgK8gCXKR`HU@8Ns0oBkKt#+Y#!m)T7Q;oQr~|wj+(H8uZng6Hlv`h#bN!aRU)z zhrh?JqXD5WXAgIWVZ8N9&GG%5R!@0+>)(=%6FF!*do>5O+9l}6kngcz39aioTzT=Y z58zJ<(Szrn%Tq3`8PhnZQC#d8syWJGI{CABMGy+448ufBTa6IiHGTut0yL25s$x@t zc$RBdEiQw$F9c94NpG?Ri5jJLUO=EvRZrW0O)eJc_uMhdT{~e^I%c@FmOV;MUN!>2 z2;>G%uxTz19g*`fDsy`9_1U$K$)}+lay7U6kDj=?X~g3;w5W`>2)5)4QUHFr6#=xx zOpM$0sri@krmyxQy;qLtq_F8>;SlagJp@iK>wVXc>(lOh1##%Q?)`=|7akKA)y_Pl zd=C2-Ww%Mqn)@(YEdj;s{hEQ|!@PIbon&gM)gM1P*h@^GpV8oLs!aHGB~@|?D!G() ze$xT_nQbn!*fC%Towbd}w`lMv6$1+I+Fc;~5@uZ+m1u+uPdajt7382@3P&dRo-DGM zwGP`y3D*p*C?;PnohQN~{~l^*?Rk!2|0(Mt8Wn@&2Wz@CYIMXrT#DV|qY{LS>#?oB zt5zE6V<1Y0E}V5nQ(t2T5cqw0J#3qATGmi1$r;o*T^lbD@c3LDkJ?8eX8u@Ydz?d= z*<3*#+A(jm+;q~c?8!NMx6E%u-aAy%=KbOcWo%|G&8R3pUIKH`Woh<++j?!zgBs5s zK>*p|5q6T1k=$j!KwfxG!3Nyql+IQ_A(N$1v4wjyB1_~6;)XX5sg`& z0`_22d5Y+X_$jhGtI3pEWWc_0#{1~QHuJxGB6;0dB$O7Icc;QU)5)f}hm(HTnW%rg z#R#hFgT+4_?|KFNZ2(v;ksSG4bva&j4iX-AU?dABIcgZ67Ys~p0IX0tcxA(!B?UY> z9o61UwNwJ{M_{SIRa85w%hjnG4I&)n2XLq<-InB0@@I;@HQxLW`OzKg9Vf}mAI1$x zCWi=WDXZ>Dj%bc-j^1BtDdh_|c3Iir>oPWOO_!VkQzhn1XDITa)D5P2YvK$-bOQ3z zr%2wdY-1-vn-;~5g4pPC(F55@_@Tt+R}HV>(w+=#Y2m$*Z*K!Ur7ZD75@o*-ASr;UG_wmpExtQ_;>KiO)e}rc517tqD_6w)$~Cz zT}{_K{|(nQPg}|jt})hqfeberc3LDa>%}TEdb$~>k3==v?%qQ2P9wEXzL76DY%B`N!&rl)5nIYMA0JZkStw?f?)GK>1GWalA zk+zZtS|QUW3qmMj{DaIXzEz&`_Ck?uI$DF`s4tdD`N86`M~0)r#GVe{c&mZ`OJ0&A zEXq*`qetW&=I0`(tMd8kcgY|Y{NuT6(UZEe_&LFNplkcFaOmkh@mj&BOxD5Skr>*T zyi<3AcbYdk7bPk@as;K_Q+MALyIP1_GAKjrL8&d|>jVw84ac}*@jVA6HRCJwc#VNZ z0myPY;#QtMqEMV1x7w0pdQIAVL9~}JKR~UJbGUkh~=doIIpBj0r=J) zNKyw{3JCQ0s`4Wkn7H(k;&&dzASi}}7PPNo+Jb_jw4zpy^2;+T&gbJSutqJGUA7zn z7!%8pq(rBo&L#M?#smoq4!vtHsLSsvp(e$KTF$(I_jGvaS6#L`S?D#~s!pOcJnVDT zsLG&ZUd`_AFy_{`OqCxyxe>!hPDmbw-o_Nnj>&Enhz#WBRvwZC(%zl~XO0C>@)Q7) z@c}(EazsB#L*d)DjaRqmcME_vCrad-g-?!(TzC4L_y-4-A0O25-f+Wrq~r$`6*Z}I zoX=E#quMgGYUoAeanJc9m!( ziK#fk=}v zV2D2N8wR7-nTEJ#c&M3J8*v;~21*)8yk6*XHVmt>3DGb1Vbi?--!DY zBxL3)j}DoqH>gMWae)0Rmh<%>57}}x7g3%z(d9VS^9o|naK2@@dtJ5Z@Q02D^?fbFmD$yRW! z-(yyqk*KS&^0OAC@g@!0DaT|ji<(EovB?VAxec@ReAa)zyX;Bkf`Q!)beZmDtD{qMx~duQC-fi`ARK@LpCBb8Jd-W9ei@rp zZ?NIq2d1~%B!KXOG;r1L;Mu#>EkWl6Pue7=%w1ITCmD1`Lti3aHov8_GOTf>itH6{ z$fA2pcpi8oXZg1}2uZZ9+Pc{p7G+JR@`2>ijE_2MS1v-xj#Xf&*S|hnN%-@FV@P@6 z?}0OJ>K6>|u+65zGIL0lfWJG_KOeHk;y{G)_0p@V?mqkm67+Al8Ha9HZS2v{k!&kF zkhIF^K4S(i5Ey$TGj!MO5+1t2Mc;yI;Og;Y?J*7EyZHkWr?kK=8#H=mfhQL@nzX?E zZ>6-vgBuP{PVRvn6wl0FLD~}+f%L)0vQ_(2@r}73*=+4u}?<8XvHo9{m zAq#HO;%MrzQ1jHW)x?`rZ-{#zoi3*yGAP<^iFQ!jYcTiWj(mAq6dk&8pK`la0js$C zG&N;}S$7570<0?4Gp<&ew~e8KO**H>6d&bGqSKeD1H8R|&RJl~9r(MoW+EXXK^5&wT|8C-(u_xRr zjRiexelzn8{;hpO`TtWw=KW-Lvv|Xt2jqK^>8}Vn+1WJeZ0*{>m{*( zll@6)Dm+&-g&LSC)D7=lM@kv6d{Yp&NsGriPm(g0<1OH=xN}Q^LTkXOj6Y#E5GgT{ zwNdIVynEw1UluD<4g^VUuZCms{{+$mo1stgHgT|0&a*KakS;6CgoxC7pTPJu$YYWR z)=;HA{Jy4#z**}*`0%X2hMa$V((9kC-FS)1uNGh)m7;$UE&sIyAkX@6I-@1O%^$~whG`9oI` z*lWe*>lW%Ib~i29Mj5=|{uD4^VMq0k-*X!((xCA(60}<7w$c8o)TCAV;{D~lRxS5k z-2X<`)$ajP9_RCoL*!zvj{my7)%_t$?tsgI22%4EU@qFjk|xr6U~7dTk4j>kZ`& z`M_>_Bk}fz!nZ5F+-e*c`nqQSBVazz>yM!Qay;?sj)PD2>JEZW2Bp}X7X{*uh1o&$ zkcRjX&SQIwZC*{<`im3jw9N38i{X6F?tqBFdjT0J4-W$?p$ANk(d7XKstuDMTTt;p zs7#R$z|>0bP-ububG#I`7{;vKgDHewa@XHX{_g_nXQ%LmBy8`^czRMrsx zSI`^A$7jbT;pn0bjq>&WbKBMe1V5xg%2&?De9 z*kwA%d(!4`uhw5w!F0FfPCf_S&s5!*BNc_+D4I^ld)>f9z2o~?g=EY&UBICeHPauy zff~WR3U=oxuYn<{03B$_SpBYswF=0Port7&T!!F{AXQ`RMcaHcWRW2B?j|z+ou8*q zvVeAgk*ab0Dw?v4DN!?`+yLD0$CJL-fgUQqkus0$4XioIq!AU*!*s;ml+YA12BXsbJg&^E@2OB*%5HW}^M507ExM38*+SJK(&J{D) zHEt9Uv{0jd+j(UHhsm}B6CpJMQlEX1vR;gStgxUX2ZHli~>Mp|x@>c^Cf z88;qM#N#T`JE1Dkp-XGsi-_861lHoqG_-feAosiH{6sk1yhMN^uscYAmO*5Lr%oEY z;gG^d0Qs%1!-42Y(F*v7ObB6z$$m-EOX#Xf@U3R_aDIGpT>L!^vA)iY>n2c>^E*9{ z{I=It%f_Z=l2f^3rQiWHw{UW9*+iTDWHu|1)ah;O_^#huGaJec*==4z*(><-={g8h zqvszkRx2QwaB3{oqAxkhZ(?#ii9=dwk#7$XYPj0FgVS-rqhG!}2VA({6ZQ89V0ZkT zGjH_IK>~;-ms!5bh}GM-xm8obp%|=B)NrZfpgz2ezl6HEmkEf3jC*x;!JHRvBDxKI zw@%r;rwj!9zTv#9kiG5OWx%38WR=A;Jr##YQ=n{qC9TKFPa9&Q!5k4u{soJ#CtD=C z%~q<+%-c=1*H)}aOb#g>fxtLIL%wQL;Nd6>*(JiB;7#5IHvhhZXPA9q%J;O4J zq_X)?6edaq%G71+2SU=hX%#d8nsg~n=8U{{6*ZjCvVHaP?50S8`&_l;>Jy0$?#Jlc zLvBGNo2hj4+IiNr@jY}tJ#0)PX8fxZdfq=9S7Xa)|JGoUy;CeV7OqrTQg+&?>@_CU zxCG6*iL_BViqwCI12R}Q?b%k{fhTev+Ju_-SE^!l(kVTZAEO%m2B)i&51|=z@Jq#w z^1g+5?ALAT-FBVBQ?~&P3=xLC4KL=QjbN>7?TLGpYIJ0I*J9(MYW6>#J=j*CEt~lL zs|&6b6{E1t3XBANA*^({ivS^MVo2m5pyHT7&g#rf0+$fu({DF>Bm#Uz!w170$e0} z5~}6Yv}BFZ!jYUd$FsG$z{X{E2ZkN+|I&ZAH1@Xpu8=7;XNKBHj8hta3Rz7{1Mb;$ z@H?gtD*atCqJe*L89_*o3d5zHXw@T?iX;gG^d)!DL;V#Pkv$nu6;Y+C+z#4Tec+tt zGVrYZja-e#sg|u)qfB1p;86R62gxGf9eI(*+073xyOeavE{c_oTEypI_*sRLsk{9- zQ#sdZM=G_f?;aD^2XgUl2ywJYUB|Ac`{98}E%oF6O0=xowjIp-W~r$%8Vkvm$2?&S zT%k2`S4YHzHH!>l3c&ir$WM+R{&HjxAe_HTLML%8Kl)`0@cB>;1R&Bt=EdKA&DmDLs{Oeg8a z>W~($n|NJiKe(xy?6d*<3WPx4%SmiO{dcs0HW#2l8yAv zhzAyQvnDUqGZkZ~ZX&YFHgK*3cRZ&kn49vrhT4rf&Hpw*U~4-c=Cp@1*@VV5cFDL9 zsT;s)B$H3IMbd0HM_=9~OJ44+Ll9IQRkijKDhI@? z@*IN6FxT&eP!Dlu;P|6IYOy2tP@!Ju%)@G`z}nPUYMR$iMJjp2 zP(ANJ_uw^;Xc^BxXuQNuiI zWL$eE7FCedHEkK({ApwlQ-qz%fZ9&Y2!TH*%Jd(UCQHFeWE)p6c%Qp;WXS4_U{x7& z70hS3-_&M)yYpDk!Nc=e$rY^B%CT72dR&4)O!ec>e#~gh!(Ag zc5+Y-_+B6BF<0sS7CnPd_Nb%nWRVOM06yv znUS3p%eSHFURy(BNu7o-)hNDKf7o5LXIeV|0|6(?Ck{gCE*i9l*>ZG;+3e3pvOnr4 zt8}@%s+}w_itd7@a%1)s`F1K`C*qlMkT1VbHwz#B3H3J1Fg7brwjI>!aSb+JOBS_0 zZ&`k~WMn;`rF4>*k9zra3N33kZXU_a8{IBhZh%KHO;vD<=;3NbXL)ZNQk}S}#RPcu zI<*;acE$YN;iaYPyuCnuH}9SRU_4T5#4~fM6Z7Kg6Wg{;lN!2R&?~!%$8~`%f6ptY zQr#v@tJg0T>$eVG^Uj=(o)9@Bevevgtw&O71-g;1`aM1gcF;NhFdbHmUlh!WMcVGu zK>Pn9F=1gxJtJh}Cb){uNw+f?Zw*u(g1w~LVp>2Q8aOpp2-1G(Tf%S;g(DT+>J0|N zF~GOU)rmaX$FiQ1lX)58R(tCpXzny`BnS-OmX{23e%a|WV~Xe3tbzJw5u(YHj7!Mm z_$8m-nY*0$vGvms-7?03S?^-$K+F50&%tC~rxb<@I@>_iMM`Mh*qdmnxu^iJn7>u- z^jW_xohS`)LI03Ek?W@`mO#^PHl#r`37v{&g}I3w{lv>hSMm!!wuwdE`e{jQ6{<>C z{`@E85bMp@Q#7dh=ps`uj0vLED9e zx-;=i?jju`uo%>dscd;kIet~#nyfMGlp3$@NQQ~v-Rp>lMUZZ7YnTsZ)yggoE<~WvHOe;m*Vs)3GKq0M1U{() zYlNLtOA*fb$5W2Vw6}b0+pqsi35{$rVz^rq=|=hdl|Cc9Pa$<&0_UjM%O_?x7EG;X-=>i|6~6sO;4g(*9)F~j*Gp(Ees_${R05t2wf+g~_2riA6k zIx`TwF_m?(My-|-SpOPxQ6Cnv*3Q_1iPlibWus5SAYDq^15+%v{KQz=y>-6b`5lb^ zvbx_7-Vt^xtgY+?!u?|26_}YI=l892M?M1xw^pB1TG<|;0`Bh6lsEtA?&E#5FK27f zK1d?d@67iT{4Q7KPVU~&li|mwEfu+@avXQ+V~cBj*G=@OqfqB#DkAYrfHF-U|Cqex zMa3G7|5-X2-|=o)eP6#Tku1@scPjfZp6_S}u} zGwCBZ$pBmr22O#fa6Q}##29Ym-mlJ1;41$+`*uEJ2FjOtWZ&>6+{^+iF7+7mEeeA~ z&Xw&?F%rTb9@W;Uy!M3@Oma+uRz3V9JLTh|b>^%uLxQKigbRRbJW^f#) zNObl1KZpDk4den|NEr!`x^&7sG`UV3^*D96SH%8OF@bat82;dXl+fN?S5*CuzLvC7xQRjNyQ2X`^*^IA z?fbf7T^Wfj7a-k&kI_XZ5wU7&z;xTbP)u!(Y(d!MhoL0Va0~J$XsLRGGMxiD;_Wid zjr*hRd$2S}({=MUF6wZV^rdN6cIFm2qVNg2tz<9U($QI8)n6X>{3EstosT0C>8Ud& zAQqT{bGwU;Uetdx*#9Y0c*}2%NhmZOdhT)@bC=J-V9~B@nt+bBWW>U3H~k7^!_idC zXM4jnrhA32g=2~Yv(bBdBSY*LY=}*rDgkx>Y+Pg=j zGC8M!eskW?a`oonPk*Ibp3FbZ;untwEklBf%-^-MX{I{bN)o>fulHI8G;PoO!_G!= zB@LNYdZqV2Xk`mb6cX61W0$q^fqTN@pm}}iTD*#D9x}l85bYXKzf`Ah7B$W_EL9ml z7MI(@7S3`)^Yv$oUr$C#oD|?Jy0$`@l%jC|5MIB#2t~V}&nl$)r}%Sw05@>5W*AWa2{=va_{tUlf7_cfGVj#IJH56j7^(gw1Y!Z|LmuZL!C-?|zvngfqOy(o&_zPo> zo6pr?KQY&CWkZ`(!os2TO;pj? zQz&73WeR!XAYA)#B1yF-i%+FjMQLZ{iRzQ=OarHULy^duhS?RM0d7__jtGVU5fA~& zH9Z(;L>6nr?ic(?2|ySV-31Qj{~OmQ55#bit);TX!_|nR@r`C~_VHZ(!Tqv1`ZnEO z`hFAdxM6x4CVr=9E+J|;|TTe!jw@phf(Z)#_>N~ zk2>UrmNgCLD8-+U*M@rOJM9PIiy{ZbR9>=r?ksmbLlif(10+@bq#LQw=}2qn9s_gh z%x}|GYOaxEOAFSS-`i<1N|(r5#!`q}Q?ZRE|JXiNRPxtIR1EIXXO{}Bos|trm&}Xq z(LT`QK9OIjS_UNHJkx)SQr1qQ}VahGRLRuAL3gpSQ~*8GEoK_T%PW)K#9%T zQ;bVk=WU*D?>(|N+V=e|7 zW7Dy_9;bLckp4|^#*t39I`$fwWSyiDQ62db{;$0K@W8ly2J}NYSVUSt0=9}rQ6zM( zTD>L`h@Pc5=vtbaiTWb4a)LwpnPkYCO06PP;ud_}av<|mb7nLD#-{1DouoJP`SuluOqqm1vphy5W@Krj_%MA2OFo(fr^!g1 zlKvCu->)D=o@0;}YS%!K;&_ivt_+Uma?qJ`fAkXc>M|l6I!XqYMs=sN5FxUw9z_5 zWNsNnl;UU6-a^d)GQZ3{!O>!ZWBJ|s0SmYWUpp0@_}8QR7KCJRnexa4+`aFJT$qj7 z7j=xvV7;O?%E7khM@iGuqs-q?Ct{0lU1WAE*2*o?Dm=BYFAy!Yn{*sET%2FoS~Bap zh^>|^otA^4&W#$l-`y4-I`CZht_9bbZ4R-T_^gesHX3#PGFgLA3Wd+MS(~gYS^S|} z%UXCh)q=$>qdBC^TBAxq9s8;i!66V72+ICM{PC_^hjqvL2%V4aq~Bt3>dL)ES=m`! zW|_|=%@v*4rla#}>y@6R!X~#nQtJFWE!}YE7_H&Mck0nxv=_N7mDv2IjT@GO?7|E7 zryj?ourtmsOtv%?$+!pGvf;T-JY7X%V|T}>4mt-I`I zcSZ-Qo9l7YCRNSgVGodR3G_lR?q~2 zs4JS8^V8B9rN2>rDh1oPWYFaikX*rKrE&a=B9w2F--%&3?spz++gJjm)-y|TdKgE` zXQhSJ{KK6xAc|pEcV&0xXV!MBzxGF1S(?LWfD}}h2GLByWpxcrM}yADEGys}AD?i) zGTUtjLE3E@c1sU3&2-xvm%z09vZa*ID5GDIae!N&U{0p_Xx7gL@J`b36kSwmyTd(l z9J#b{+5Tbl0@5B^fdhc&t+L)H>OxqlSF8u2+_)rl;!yOe2HO{@2S?BsZwO?`|K=IG zbKzdBW0jsn@LRY=C!ftrK-(iC%mExm_hYe%EoDX@1Ann?xJ=CM3Oj<@Y&m4&5TqrT zcl_x@E1SGy2SFy<3Q#%bw9y^M#KQ9zEl_H#3xSsv0&E0qLMmrHMH5ekXw(nhgeLf} zMfF$<4eo#R$;ID9XQPrK9cBxzK3J$D@Bn&%P7uB*KJ2Vp@8%3%!y$vmRO=RPj!^zjFw`qBY1 z`=};V%x? z^a#-?3TZ2&q+fNcD>mGM(h8~fJPMO~o=Od}ghG^op||~5TG`1vZ``wn_81$z@*89g&;eyW+gqE#Qf5J+iYh# z8JFX$z1V8esMf@dlb=veFsmD5k}nF0;Yt6YKIV&hv#;Y+>7n~rNdK~^n8x@cf3P-V z@a&|FZHzWXReBMd=Kj5z@xV`(O#fz68S$p<@5+}uN(2~0H-Fe~87O2QbFbUmiUs|Z zTax`dyE5-i9ZCExpL8CMn+62my}D3ktFZ?2L_*eUAPp z!@l-_AR+Sk{bs<`sH|}et{9}V3-}|8LK^Igy?G^kBXrAR8y?f`L zQCev0bh-6?ZM$!VxdHJANTw;>z4m zH8YKsTAt)4wzyqYl)(Y2G*UQf|ZpvtI|_#nfvfwUgX6ejzJ&8P7(p)r{xpC-H>fdo~}0i&;5s zvVAWocjn6pmeUuQ-JhddS5o zx0~GCa-7+h!7P9d)+j{WdzA1LMS(5+E4X%E@WS z5rBqtYBZ6^l^VrHzOK*>zMlWfJ+UBY1P^_h-!{ccy%-iS(iK`SBs@x0m;=S^k$#Kl z<4_s|0qndSY{X`J0$mfCI1$;vF+MnpFkto zXj2?clN9wfDv7EB0XU3@_j+4}Lkj6S?TK4uG(E($j|*Y(Z+_sgvVm0D2NDt;ded$5 z;o%3`V_BEud)o4`Nbr0XcwB@%!?7VI+oDW?D`}($kz|xj!uL3FqVQA1W^gjZCSAE? zoWkS*=NYnMex_p-^MjX9HDYh@^)^-2prTBYPVCiXxr8fs`u%n*2M`Lu!-zEYnVhe3 zl0?}|wD8L@Q(OnavCYni{Rm`^))#motvO+L&oWM4!TV#Y#QZrp=B6{QZ%W~1#)8WB zsbE-mTqG>yKsY4WQS6(6%=S=C@$c|-Ff96P$0jPXIMqv>L!9 z;uh;P({54ptl-DqCw}Yt-Te4|-i> zRA6F_hXICZ%zG`7xWTIHbzqd)8{ZgtO9mQKa)qMHJMdr<4XL?BhOJuAK{QY^0x%E| z6!RJsVP#GPuvWvAnL*JEM+~_OB$*4KM?u>La{Klr9Po{ZhYgt=g+`PErMbO68K|>y zO~EVLuLBdOE@}q50Tixz2?A@PFUX>ln~nmM+_{0oRW+0q)6V?KviE3#`w54z4MGIq znq2UI$DArv0#I6^!WyiM2~_V`^-ZjAqtsjVpR5@PmCeUQQ+p^`uq%!SgqlFi=vE>E zW}^~L_dFSz@b1yow?!5-0WYCP?d@<%3+EUp3X;uGDz8zCM#-eVlM-+vk%WR?J&07U zTH;qkZG)fW;b&z;=!pq>lmV0CHml~FedDSNKC`^?EK~`>Z{UL8TxGwijE@4l^2`yB z_muL~k&F!3D~Xqgz0Hog3It$(pd}T#sWifi(Pp`mi5G+0qE$mUBFPA1e?#ajA99fM zk)baJa>MP!m)>HVyUmK)`lBS!-m7$#DZWOEz0^yaVCg$etrSF2g;QW$kO654omPIb z$bo(4JSNGL3Ya9ygByOKCQIz%LLC;-k1l{iHJR_C9G3FH*;#!u115h-LHj_^n^?x= ziX7)e#fB%2&lr7zfFG&zB@buB7sL$f_3biYbOrY}VtD@!qSMc(_QNa{G+_D0BJGLo z-$A8nW$j>Qyd_r!^Akyrtst#_{A$!ZRCR%%R4u61{H3^M(xJDouQ9j=c>-fcPQ42($#veifGZ!VjYRp zRUGO=j|aa462FDJV6>?&8;3^QkXE_)Yg)1n% z4A$5M`#({M1LTm{{9LLcK>!}HQGS~l7mwKdlO%*v$=(Jb?p~3QysDO~JMzT8s@#@e zq4`WXD8faZv+mL}b%Q}b6*m zaS?P^9MLskoby@pj2)0n$`8@c$T$S0UF3Yp|L^<9J@sC}25YNdZmzD3e-Ya8Y_IAS$rqWNk@?Ej{`mu!Db4g@0^9+-O^N!4{12bQdw5 zC+Pe-ydwhl*Pj9?>2ha#6yOG^72&-6m}X`eWP(k;&nROu)Jn9|eA+iw;3o5t7~;}n z4>&Q~5X4%%vaDrUKrI+l#iX5qCmF0>v z`zM(Y!yp`G*#tO6aqr||>L&%{;L*rjOz}?laJnQuL9AR>CGWMFS%#dEAP=jz5@FU3 zbWgvtLY9k_*m!1B4+;^pO^b`@^pN6NUOgCmQ~6BE9SYgRZE8`tW)z zPYztBUKsg1lSdIjt({7<+vndmJcK*cIq~tg)Lwn7YP^w`Ae*vyrVwTM*YXH4GX}~m zL|ibD?SrZE#43`MXh>5oF}2OqqXY zqg#?SjSepq~H7F_H9yNSLFpt#BeW#c|Oitc*x-CzDp1sdgX3M`KeOv>v5rr_S2ffIGmUyM4#o5aispcmPIc?K>L<$_|+*Cvt{rYm8Z z(d`h~{Ov>%8;yj#JQpy4v;Jp}Nb04dZ5&cHmf7Wdv-n|V6I$j>fypqtZL4e^G7!a> z@P{Zh{bJhY0)={d$rO(5uH-g8hAaTRi+-0akdvx`nw3&jkqxT|re=@Vcy zI<3&}_m~4R;{IDvn8lU1J?9qIOI&ClQ8#5Q#;{w}?c&GkV21bes`v8&9muIhR9`hu zu4ykNQNZ>e>*%bRWyNA!G-`Q0bM4YPUBJM`X3%ufQVl3x2%I}=ORpbB6$Z}ZRCu0| z#~k4NgZM8_8b1hOEb;l>YUOQa_h5zo6)~vRNMcGn6qmG&%gD2-5!zN_Wh~Bh%un@^ zUoV6owCMQ07YTGF_?BHFl2Sks$0C>ktV<+%9LWj05n_swY>O9VjaX7ilv2C!Uri$} z%DH|oYVbJ=QY915g2y!+UCNL`tvYer zl}CC+a#ZAvyf8K%;7Ze^jF#Lr;D;%LW*N>gCy(;chGA}%2#&m#q%4O=-ZL*hmkai% zd7DE{$WAXou(fF7E4cR6Khqmch~Kjkw%ZWJUqH=Ha#j!oF)8{#l)Yn=ZBh59SGI52 zwr$(CZM$ySwr$(Cam%)CGxc_-lg_`Bk>reh&Nv_T{U1`)(a}w$mZJ5IRrb9bS;f2tzyd;hq>sDsT1r@AXK(tiTsdi?1&C ze8OPsR&`kwhk6LaBGh#NRisfS(EWVnS$QWO|Kuf*7$(E$v^B|-cx}qPN~~gLdM75&$lFiX}55n zSvexFjyG->p=c^JDYNFK>^$;EN&^a}AH2sa|AM3gtylob1TZPs&8G?oIS0?|BJ-6v zlY5TTt%(o4Ls?7G(_c85DZUa-(bj_R=?fe z2jkU&B;nK1XN8Y}N^e_5DBJ;w3A~*jq~*rW$Lm3CyTI{+y~532rLong8WD%si$F1q z24!H8I*fp#ba|a<36U%~q@k)jI-YY}_&6Ht)a?i?3F;3E#I>}rYkaFi>^>|nuKglO z-YFfgdeAv$`9;Zv#`FWeVeRO=%9h`yor3GAwPqbdGz_uIm^jU4^?FC(@pZ}?K>5c- zKpG>U6Jy(K*{iLw4J_=k{}6cEBNqC~*!{Q6luDZf1D$imW)&<1 zNOsmNIqV;D+xx_Q6Onuj1V{93isj&bjkKCAewU4!-xUuC5u2etsiuLgxhxPY%_8>I zXIE%HYdOScsZm^99@C$=E_Q`iI{g_s!`qko1sI%|A|iy<(G7cN`aZjP%GvO3@nyjk zcWH)(JTQp;wD|CYPaf;XN`@kg{R~0(kzml?$Ud-Ix#H0VfXfF#8hMuV?^7sEZMy)@ zJo7T#vQwzaxfks8%G6sy<#eT2zF5?neBGbR6PTlSEid0FeR7O|{B#7EJ+8p(Vui}4 zIw>oN5CDkc-^&m`32ZkW#($O~h`?O$X#pTeHlOTx1qF*AVRDv)oIl;aiwf{o<4&cl z`sJZMm@O7{8y=;@cBq24A?V8W4Bl z_aXiylzHGVub1y5fhFC}cgNw;Cs0zZpt$HtC|cL2{s2`S%=2x25UWX^t)p58)y^=N zbCB+S?No~pAf(u47gF-DwfflVZ!D5HM`PfRgO_~NLXZH~S0RBM&PD{& zQnzx!?-Cc4-g)0Ciohd5Hud4aqzL8ulmM(_(^i^a-(5B^ByAgGc>Q!we?)5u{y=Sn z?!5|U##3)XOkt~!TS*VTLa>ey4BpXlQcXF&kVPk&Y6 z*!ZajUfQMqWzs}@jfIhix7q{i?$dXy)Y+P|GW{ID{J!wlM^7&h+He=+UyhH*=Mmy} zA2Qwcc4vaeFBHP^Bxf9WdM{*iq#xk|*wBfzJ;6&`4$YwU z2#qg`;sF;WK>JBxzQ0$0v;sbr6SK>Go3AeJ;pYBSU@r*s!h)NmA$k7-K6Fw0J4Lb>xXAdHHfIz4eh4!~?IQW2of*;)5tr=VqE zkZo+}(!cRXZKco9$$mtuNunJ9<`DX?FGZXz&<~noC z%pt*{UV_TP*%Uf@0O;%JY`C;5Aq6doFg<(@12B6#W&F+t)P4`>Q|6Cl5g!R1Reqyk zykh?E!X{J!fgLo|{zg0)+p!-e9!ESclySUZg z{-MY5ViJJ8(Fe}4as&+lR7R*bX@k~P!@aTi3Ll{^YyCLOU)16|s47~LM%?Dt-? z5dg9saQ6HWV84e-gF(a$LO+dBW{UXH&yQJY9mAcCcUBiZUv ze%nMZ2_Wx}2)dMA)aP&|gNyCM=z>iO4^w=f9MLaTfF0?__u>#X_TUf_*L2rqnhjbxtO7MqdYX_0@-pPDu zigiOd2>huzF|j-}k)_X*29;!(WclAvO)4E|sg~T1`V~ENE-A}+Q`^hA+fiyn+&X=! zZ?gu*x8^)Sn-H%DrNmy0L1&adf8=nppsp$%51PgTZz;&@BMOZ$#|oK_Yhm^{%ifmd z06zZP<@I^<2jC-{h}Gy5G@he0)5 zIb1z*wKFPxXLNVN7l#wXUvGD&cOXZUISf&s<1@c*Cvev*v;r25PrybVH0bL&@b8hi zNLS(I;O7rw;LgzQibw^^$o&}1C%OfwP}e`-pL|b13)tK$&|BZg2a>K-gBiMpSu77? zAF&~(RvW8Kp9mUC+2fB&2X6zem~;7KR8!fJ>uJM_upllX$Z*_lo6ugeflhT2hfpNW zgZI%*`JY{1KfnsyPk4q`H}uQjnEO*%+RM>0Lv#sth1+SAJ8%CVuPK7)!&rKJ9b#49 zyrfyf9!#2OA`3#a+Hb}htVh$79LWVKSs@)_c+8MjEkAeNaO#D|idmW4VC;FRB*g9@ z#mU>bgMUC@VM34N5vlb_92h@Pk4P&1%&6Zunh$KYNl0wsd%gWPgj=Vz%AS@ zs+3LqQAGfl&fW;bV;Dl84I%^8>js$ktQqBkPpSHVouwn76=0k$ymJj6M8j~9Ug?+vfr18DZb*OBfGjZlrgn!%0UB{Xc>mWyP@Q1^(&Rt@0I=if0kMPlDrGd+&y;ap_fjcihiMfKL*WWBLFHxloz9v=L8Jc^~4 zH*s;jW68&Z4=mizO6ct2kS}eyUiJUDxL=cpaISCS4N^UD3HUhDrQAdt6^mspO)M-x zOKlCEIdV?t6=)l&C7evFr;6wc`*(+hH942Mra+S^r=@7AaiB?HCTr0rn(|}TgrscJ zWyu?rWua}dl#MdRvA+!>;1#Q5M5s2=u7x2)IGmz78U|mWmviC^n^TA0z_TO# zs$Q0JK~?M#ZsWv^a>2R%>VKAB#8I@J-*&TCPQB0w)Gt_huT{5j?wPWmD&Wis7gf83 zazoR9(zM^XDs&T4g}?R@FKhm#ZUdAaz;pKAc`-xxzc9wWLj8o*-p9U8J1|G$4+x+P zM98f}RGz~1znDnZhTrQ6M9I@wS%XiUx9e|ygYU5D`zsJ9%|JGfO@=xHY$LC;Eus=8 zQ>UKDTR{{0t8u8}TQsFhYrrKD`A3hIp58^Uaal&OEd7ouj#aO*B|HTgKn`heZJ`@c zq@bqHW&!&2z#V4_G2k$;X6kM=pGKaaL_R$evdjjAjz->qw?R+yRZ^&$-kB($O7CCX zA`uV&vp_g%SommRu{AK#m104goOi*c{dF*3qZ6vzRiNZbn@~6! znQ3i(-O}4T_=i}Q479hC7t(4|ey9Gp}d z&1}1J-AgBwF9im^k8^!PCWonP(DSHVpVFfsOCFWE1iVrvOehFA2LTLTuMuQ$wieh~ z-K}_k-?>d0yaS;k24)feUhM-kHd6x#a99dew(l_QsJPA{R)hO^{nWEKL9Dopt6lcO z7VzkJ|2v?`fG>s?GF<30VCp>Y4xywY$UM3~kP0qeJnK-u`zs81P}Uu%h_lrf!3k^m z^TE6~iDPH=1gR~(L3v}rOYbl5`~CJ=;I#3>cL;hc8!!6{n`9fsWf^POJx`n zWoBC~jC=Fa+`%*Ix}Ym{0lK@wiQfOUI00q&;$Z$3rw>p7fZse^0AmYNQ#~VV11Bd7 zQwtMEIz2sm4!U#s7t(-J5xD^2kb-w$Os)Q$%pOQ0_uRqRy@yhuF5 zSUlx0M50Uj-d$vjl44h?C~QR>QZEFh3$F4NisGQMD4;f;JA_W-D!j5S(@FU3%O2&voB1 z!@D=m4vE^>(hhQ)R|AU@<^;pQ(#ihtzoeY|!ZaF_gLN*A#%K^at`F+R5r)+oykO>I zjD1<{;}AePkwsX;4}EMHL;sB%VFQ#LeEPjI&h9loNOyxnA%qLn%ORtZo6A$ZQ>~6Y z5hAk=Q?Ng&A^qF}!siC~vH@pTDO`Sj{*NwICAPwduw?wiKGrOcBJQ4*e*j$!+0f6I z5hMHgwDQ=1fe;~3h8D1NFyYD%J{T*HKnClXvg|f_BE$^rBb;?fulxOZZm~x;fzeKz zX2D|5mh}(cZyhz^xJQb<%s)GzjJat>?9o^LhV~HeUFdt7A&(+%lrM$Ya%0}8f#>UC z!Hh7wuBkOcYdI-oZ<+dL)W%{Ltei+J+<#oYfG2%_fYl(9xw5ZksgieLb|M@1tF!P% zft&Z+sE8|y>)d0RUe^xW9~ZU7tH8x~t4?jb{s zKoW*;JyJRBR)o??R<5|qqG>~lrC4Q6M$CwlOe~TJt-|EE3$t>n4v9w?k$N_C+Hlhi zdgOKugm&j2bW(o|fU3Z`J~&pVduKuHGkfeyMDs6 zLg#tuS-Qox;obS8v~n2Uts9AknSFAlI%>X(RID}KvDPA;!}$EZ8d6A zBT?FFb$3{EmmNmtmU-1!zI}1~2)}P|e<-+8=4;2zauqVi=AO~UZ2x!H;x6}}RW72A zBhMUBj;XVLBs(2#*yv>=Zg4>sHqI?IXsGx*ea>0>zvm2|g^Y*OEY0|<>CG1*#^>F9 zV(JU1<_IYA#M#-u<{0@-ii;5y1N=8!Tv#-BIps#NA0RbN>DXQcsk&{Tq&T2yjJ z0ssi20|3DJzo|6F2F?a_&L&RIwC*<6!I~S2MB|9wta@{H9|0RKy$Jq;=tI;7U?X`L zwO@i`vyD`A_|z;1I9#7Myy;6@D#aFEf1toRif1OO63OID&-N{zj8?d--k0Y3-%K|i z`CO(mYI$mNIzkJN|2{djZq{7;Za06uJ9*vuEbaU>`Fz*Eb@^OAU$b7E9DloXUgdlo zKOEoXZ?u5lbWAI)*!AM#Fi*ozI|TX`yxA;>AHipa>e-951bpv(>-Dz(G8?(=>R ze)+1sq#k+yap-+s42F;r4bbfk# zZFp4cvszQi_h9V?%?yo9&Fus&b#i!mc&YQoycE}M%NLxD&#m&?WrL;iWa(hV*G-EW zN&$ngu5+hG@BJyYX6>ocr~6{%rSsUN^Q~c|yYSe%)suJVUnt8@ZwX$d9$e;8i5`z( z?8kFm`xRaX-PPmN$28M^%Z0&BetUNZMUHES$M#*fP{$muwoS%0;Oo0-@D(7@S#+`Q zIWF7F#rMTFXQo^i`?p>8_rA-5UGDBSa4iGR>z+$z!4);G1FLp`6LO?Y?r-D-Hmlji z*5tu?j~7tJMqps%iA!(D2UpDXd;Lv|*ut=HPYtex&6($e$4_m#MvpIEET z&ZpJZ&*JG`_=jCz#m^^VnZ|eXn>UdN)#*gt1)AFA@RE&pWWe`_Xw&oVF7T)N4iOjL z%0E8fI5CR4txW!to!b%DmxlH0j?pD4ugPAWoWJP2oCDh(y*sri`~~n?8eOFud*3HCmNPYL)w#NOA4^WH z9$P#- zP&(1?NLb(1d0dw@Xe@E7#L|}K`CLE7Fw~pcs0xTWEA&BS1PWwdvg{zK_i(h%wzbam z&;G#jON=+Aw4X#gZX#P};!2^-?++!!DPrj#KJG^u@jlMA@ z%8fG zOOR|DGkbDAspi; zl`&Yrq^W1e6U&^lKw+U3$+i()p)3qlvskE0w1-0V9x_|Q&xzfoK#$3Rsp!wKG`b}m z$g3~x26z>_Vdf$DFxNCP@?4wGfwq=!nWbMAv*PJA-!m90fsFMvRtV`YvMHWZZ%Wi9 zNnE9I$Js53w`?E>UBw@JtSG2p5n~x3PBHIVxh#sM!9}X$wd1ImfFrHvV+9GnLhy;O zgpl7}F_L{~8jib2#8K-Hmw8HN&AipkDuQCLMMYGF4%UBw3gvaX3S zQ9nRlna{&}nIkCbhoV6aU20jkIO|?a=!YUENvt0xK`VLF5jojmaOHh zG2pLs5!JlO-UWfIX-4eSN93P&cv?^;R2(A-jGAD*YysUN;h^7AzNHUR=5q?J&GgOdh0{94i{@0NP_szwFM4LBdfU7!}Z8 zIB-a8<`u>%4M|w!+6VDkx+=a91EWtFXgdX8#f=}s0GfOc95#zj*Kksz1CkLZs^1@l zZC(Bd$Bot)8c)xf8ZS9CC*;4$FKUt7g2<@Ikq>D(<>csyC;&hrLAuv3c32?9%cWtU z60KitbY7Y!nh(%E1i0#Y;KxX))lzme1h@>^M+`u{CKY@-8CJT2tslL+l9L*R4QiN} zv`TAJ=nO%XhVt(&E5Wp`Ov^qHdfoDf46mLR5aNN5G_W9fy+lof(^VV}F1cQ^UcgaP zL}=)W9nYjbaZD3(1{Tz@K)Foc_20fYmx?+{BV5}?Mj6h1Kf?ui0|cCW7_t!AAHN~W zI%G4ZdQN2=Y84ARCTDrxA@n6nqH0W5#X??@)#P}#1Swuh%c9Rq3Rp2q7XiRQ2&)=( zTn>ExnMka|f% z66^=

0jFY|zJam!`C5q$6YtJcS*`^g?4Ogg}UvB2}hIE=^cPKX;Y_DDe@hoG2Bs zqb{_l6+KXwp2VjpRE2yi>gaC%oq5%K|mEns2 zNM_AIJB?i+a%U7lwb8`K_^k(kCC@vw&zmw@V6!42^=A~k+~RXkW_3u5an*Ztr#ei* z0(xWSbFCGTu{GbBXNPTh1uUSe!}Eg38mHhcLcRK3lP8t?%S|oC76Fsj1!O=KhxQGK zNnixb&!C#9;?C+LF~q8(VgOwOm{cIumhA{B)NH`>F;uGQg3X3VIXqea$s}$UKi5v# zV^{E_agW&#EFpGu;s8UPMQxNIyi#w>)5{Anv;^laQR%;b8W1e?=D#c}E_)vE*R;GZ=2l>yikhh?A=n4oFWB}8 zo3G7+G?9Et9@hLuJ4JmlQND5iT-LGYK;j-ayGIBf<^*Um){g~$3HjWM63-TjTC<9r z$LFvKByjeUV|x$HKx-fu1osGGaZ#j$WjN+fmqT9cK#+H1#~yvt?XXt&2(7|sGJ84e8*af z6K#jhSy(S_tn#NGYFWKq52P0O$6i;(!P`hb354<<{q9dxq%e(eBC}Tyq3h8Kez*i;hkx@o}(lzaMq@A99XHS-mzv_0vH(h*1LR% zg&CH^AcY~nUJls;H!{Wq-Ky5yY(WA`N{BZ=1|vfyW^@G5Z4eY^W1{81plS??T@M_j zbc|aTA2nvWmnCJGJ<(vl)8iQzXGBJDUM0q)qwnY}=?ruYxDHnQIP^{W4<>V7;wG0k zFDHhC647;eWkDg4!F&cvm!$Fe>Ks;a9&3~HW0{NtMqLF1Q02UGW8^Y2B!c`!SqTXy z<^!VXQ|P{PwsnzAMvw${!)RXd0?Nq}MB9MQ3K+yI?zQo>0jPl@;@_aQ?f~;V z$a3XVc?(SS$Be}?TID2=zo$h)qmm-ivFu9f;k|`Sv&w?-G}r?X^L|U2p0>gwATktX z3X=g~$eMKHO7GkX7)F#8$P&OPGP0JS7MLmt=(r{@<*O+Au#+{Un5skW?1wv2nMp(=&vHHu`k1mw>{A}&$vmG=~%*>i*< z%>cJ8j|ut;`g$aeu!$tQ2l7svGtG~KPQXg(p0A#Q08SOWoF$%Vyr+rjF&}{%|Mj>< zoyPjZdmcpZ19K}4l)&Z zah2vG?m@}2emzI_beLyWE~5bDFX4WWOm}pq4r#MRS3FKZZysk_);ALio*`cmk|Ezn zCI{K>-bi&kh&tR~xViC;ei?mEwTP}BM*VrDgga{iT&^T{Fd>=CX)F!UUZQG=ZG^NU zq%&S&l5o{E?HmB45-6UKgQj#5`HahmJ?*!B;Vl3Fw@9LbT868VsY-5LHMC(Jc=Hs5 zx6V7H1ZPH$Z)h&2Or>XGC}zgI7&^HbPKfNs%3`MyGnL?q94KHeB^E%KZKe*DOQ>Nx zUt~&XlkwDuPGhDYKO*Ansimm-_h1aK`V2w?T{!liWUAPF5Ds}7`tu_N2p=~5RBPdo zAJ8B)W(Q}hw^3V}=ySeINeV+XudQ!gt5|~6h)knwn=$yh-RT~d~^4BBX>Bu5Y7Fi$$YoGl{hTbi!72^>~kWq zG2ht*t}fR$Ez*uV3}!L`tg|S|sc`&ac*9trpJ@{5vP#3h0L5akG?@knoS@p0eFKS- z*F~0eZfH1MpyYs@W>#`s9+-Fw?-ssNFTk+5qGF^tFf?j1eWYRFg7q65AuCWAa`G+h zT%hTpQ!arPRQfkBOyGgjN$8#Zgpl!9`fT3m{RG~p%0A_ZV^|?Ug77c-;8|NkwE!8| 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 literal 0 HcmV?d00001 diff --git a/dist/diff_classifier-0.1.dev0-py3.9.egg b/dist/diff_classifier-0.1.dev0-py3.9.egg new file mode 100644 index 0000000000000000000000000000000000000000..9632d366b9aa82879fc45653019a0333af41bfd1 GIT binary patch literal 107123 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 V0Am9PL=U_mz|9J4%+amt{{W7I|2_Z! literal 0 HcmV?d00001 diff --git a/dist/diff_classifier-0.1.dev0.tar.gz b/dist/diff_classifier-0.1.dev0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..06599862b92ddf16203c4032546f5bb82107f1be GIT binary patch literal 45284 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 P=kxyo4X5gY07wG>yLs@$ literal 0 HcmV?d00001 diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..babf605 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/diff_classifier.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/diff_classifier.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/diff_classifier" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/diff_classifier" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..351ac4a --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\diff_classifier.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\diff_classifier.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/doc/source/api/aws.rst b/doc/source/api/aws.rst new file mode 100644 index 0000000..13b2c89 --- /dev/null +++ b/doc/source/api/aws.rst @@ -0,0 +1,9 @@ +:mod:`diff_classifier.aws` +================================= + +:mod:`diff_classifier.aws` +--------------------------------- +.. automodule:: diff_classifier.aws + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/features.rst b/doc/source/api/features.rst new file mode 100644 index 0000000..e11e4ed --- /dev/null +++ b/doc/source/api/features.rst @@ -0,0 +1,9 @@ +:mod:`diff_classifier.features` +================================= + +:mod:`diff_classifier.features` +--------------------------------- +.. automodule:: diff_classifier.features + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/heatmaps.rst b/doc/source/api/heatmaps.rst new file mode 100644 index 0000000..ba47675 --- /dev/null +++ b/doc/source/api/heatmaps.rst @@ -0,0 +1,9 @@ +:mod:`diff_classifier.heatmaps` +================================= + +:mod:`diff_classifier.heatmaps` +--------------------------------- +.. automodule:: diff_classifier.heatmaps + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/imagej.rst b/doc/source/api/imagej.rst new file mode 100644 index 0000000..3929f78 --- /dev/null +++ b/doc/source/api/imagej.rst @@ -0,0 +1,9 @@ +:mod:`diff_classifier.imagej` +================================= + +:mod:`diff_classifier.imagej` +--------------------------------- +.. automodule:: diff_classifier.imagej + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst new file mode 100644 index 0000000..2eb7811 --- /dev/null +++ b/doc/source/api/index.rst @@ -0,0 +1,14 @@ +API +=== + +This is the diff_classifier documentation, auto generated from the source code. + +.. toctree:: + + utils + msd + imagej + features + aws + heatmaps + knotlets diff --git a/doc/source/api/knotlets.rst b/doc/source/api/knotlets.rst new file mode 100644 index 0000000..7b56e60 --- /dev/null +++ b/doc/source/api/knotlets.rst @@ -0,0 +1,9 @@ +:mod:`diff_classifier.knotlets` +================================= + +:mod:`diff_classifier.knotlets` +--------------------------------- +.. automodule:: diff_classifier.knotlets + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/msd.rst b/doc/source/api/msd.rst new file mode 100644 index 0000000..e7724e7 --- /dev/null +++ b/doc/source/api/msd.rst @@ -0,0 +1,9 @@ +:mod:`diff_classifier.msd` +================================= + +:mod:`diff_classifier.msd` +--------------------------------- +.. automodule:: diff_classifier.msd + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/utils.rst b/doc/source/api/utils.rst new file mode 100644 index 0000000..0df75a4 --- /dev/null +++ b/doc/source/api/utils.rst @@ -0,0 +1,9 @@ +:mod:`diff_classifier.utils` +================================= + +:mod:`diff_classifier.utils` +--------------------------------- +.. automodule:: diff_classifier.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/cloudknot_parallelization.rst b/doc/source/cloudknot_parallelization.rst new file mode 100644 index 0000000..62a0d54 --- /dev/null +++ b/doc/source/cloudknot_parallelization.rst @@ -0,0 +1,13 @@ +.. _cloudknot-parallelization-label: + +Cloudknot parallelization +========================= + +Diff_classifier also includes example implementations of parallelizing tracking +analysis and visualization with Cloudknot, a package developed by Adam +Richie-Halford. Checkout out the documentation +`here `_. Some demonstration functions +are included in the knotlets module, but can easily be built upon for custom +parallelization particle tracking workflows. See +`this notebook `_ +for an example implementation. diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..17e40ea --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/stable/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../../')) + +print(sys.path) + + +# -- Project information ----------------------------------------------------- + +project = u'diff_classifier' +copyright = u'2018, Chad Curtis' +author = u'Chad Curtis' + +# The short X.Y version +version = u'' +# The full version, including alpha/beta/rc tags +release = u'0.1' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'numpydoc', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'diff_classifierdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'diff_classifier.tex', u'diff_classifier Documentation', + u'Chad Curtis', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'diff_classifier', u'diff_classifier Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'diff_classifier', u'diff_classifier Documentation', + author, 'diff_classifier', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Extension configuration ------------------------------------------------- +autoclass_content = 'both' + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/doc/source/example_data.rst b/doc/source/example_data.rst new file mode 100644 index 0000000..a8f50c3 --- /dev/null +++ b/doc/source/example_data.rst @@ -0,0 +1,31 @@ +.. example_data-label: + +Example data +============ +We have provided a small number of images used in the exmaple notebooks in the +AWS S3 public bucket nancelab.publicfiles. Users must have an AWS account in +order to access these files. + +We have also provided a larger collection of images for analysis with +diff_classifier at the UW ResearchWorks Archive. These datasets were published +in conjunction with the publication "Colloidal stability as a determinant of +nanoparticle behavior in the brain." These videos show diffusion of 100nm +polystyrene latex particles in 0.4% agarose gels with varying calcium +concentrations in ACSF. + +Downloading example data +------------------------ + +The files can be downloaded with the command: + +.. code-block:: shell + + wget https://digital.lib.washington.edu/researchworks/bitstream/handle/1773/41669/0mM.7z?sequence=4&isAllowed=y + +The raw files are compressed 7Z files, and must be unzipped to obtain the tiff files +for analysis. This can be done with the pzip package: + +.. code-block:: shell + + sudo apt-get install p7zip + 7za e myfiles.7z diff --git a/doc/source/features_analysis.rst b/doc/source/features_analysis.rst new file mode 100644 index 0000000..f31562a --- /dev/null +++ b/doc/source/features_analysis.rst @@ -0,0 +1,69 @@ +.. _features-analysis-label: + +Calculating features +==================== + +The features package in diff_classifier calculates geometric features from the +xy data of each trajectory and assembles them into a pandas dataframe. The +current features that are calculated include: + +* alpha: anomalous diffusion exponent. +* asymmetry (1, 2, 3): various expressions of the manitude of asymmetry of a + trajectory. +* aspect ratio: the ratio of the long and short side of the minimum bounding + rectangle. +* elongation: a transform of the aspect ratio, one minus the inverse of the + aspect ratio. +* boundedness: quantifies how much a particle with diffusion coefficient D is + restricted by a circular confinement of radius r when it diffuses for time t. +* trappedness: expresses the boundedness in terms of a probability. +* efficiency: relates the squared net displacement to the sum of squared step + lengths. +* straightness: similar to the efficiency, relates the net displacement to the + sum of step lengths. +* fractal dimension: 1 for directed trajectories, 2-3 for confined or subdiffusion. +* Gaussianity: 0 for normal diffusion, deviates from 0 for other types. + kurtosis: +* mean squared displacement ratio: 0 for Brownian motion, <0 for restricted motion, + >0 for directed motion. + +.. code-block:: python + + calculate_features(df, framerate=1) + +Visualization +------------- + +Diff_classifier includes a module of standard image outputs from MSD and features +calculations. These include: + +.. figure:: _static/trajectories.png + :align: center + + Trajectory plot of tracked particles in target video. + +.. figure:: _static/msds.png + :align: center + + Mean squared displacements of trajectories in target video. + +.. figure:: _static/heatmap.png + :align: center + + Heatmaps of trajectory features associated with trajectories in target video. + +.. figure:: _static/scatterplot.png + :align: center + + Scatterplots of trajectories also colored according to calculated features. + +.. figure:: _static/frames.png + :align: center + + Number of detected particles per frame. + +.. figure:: _static/histogram.png + :align: center + + Histogram of mean squared displacements and diffusion coefficients of + trajectories in target video. diff --git a/doc/source/getting_started.rst b/doc/source/getting_started.rst new file mode 100644 index 0000000..d2bbbb4 --- /dev/null +++ b/doc/source/getting_started.rst @@ -0,0 +1,72 @@ +.. _getting-started-label: + +Getting started with diff_classifier +==================================== + +Pip install +----------- +Currently, ``diff_classifier`` is not on PyPI (though it will be shortly). +However, the GitHub's master branch is available: + +.. code-block:: python + + pip install git+https://github.com/ccurtis7/diff_classifier.git + +Git clone +--------- +Users can clone a copy of diff_classifier with the command + +.. code-block:: python + + git clone https://github.com/ccurtis7/diff_classifier.git + +Running the setup file will install needed dependencies, including Fiji: + +.. code-block:: python + + python3 setup.py develop + +This allows you to test the installation: + +.. code-block:: python + + cd diff_classifier/diff_classifier/tests + py.test + +You can install diff_classifier from the `Github repository +`_. This will install +diff_classifier and its Python dependencies. Users will also need to have +downloaded `Fiji `_ in order to implement the +Trackmate functionality. + +Checking Fiji installation +-------------------------- +The tracking portion of diff_classifier relies on the Fiji plugin `Trackmate +`_. The setup file should install Fiji +automatically with the Python plugin +`fijibin `_. By default, this is usually +installed in the directory + +.. code-block:: shell + + /name/of/home/directory/.bin + +If needed, you can install Fiji manually `here `_. + +Note: Python2/Python3 compatability +----------------------------------- + +The current diff_classifier release behaves a little funky. Most of the code +is optimized for Python 3 and isn't guaranteed to work in Python 2. However, due +to some undesirable behavior of the Cloudknot dependency in Python3, any jobs +submitted to AWS with a cloudknot.knot object must be done using Python2. In +essence, diff_classifier should be run locally using Python3, ec2 instances +called upon in AWS Batch should use Python3, but Cloudknot submissions through +an iPython notebook should be run using Python2. See example notebooks +below for more guidance. + +Examples +-------- +You can find example implementations of diff_classifier in the `notebooks +directory `_ +on Github. diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..6e6ef99 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,129 @@ +Welcome to diff_classifier +=========================================== + +.. automodule:: diff_classifier + +The diff_classifier package is complete particle tracking package implemented +using the ImageJ plugin `Trackmate `_. + +Motivation +---------- + +Multi-particle tracking software packages abound (see for example this `Nature +methods paper `_ comparing the +results of 14 different teams). But researchers are often on their own when +it comes to scale up, analysis, and visualization. Diff_classifier seeks to +provide these tools in a centralized package, including MSD and trajectory +feature analysis tools, MSD and heatmap plots of output data, and +parallelization tools implemented using Amazon Web Services. This package +is the primary tool for tracking analysis of nanoparticles in the brain in the +`Nance research group `_ at the University of +Washington. + +.. figure:: _static/summary.png + :align: center + + Sample output from diff_classifier visualization tools. + +Installation and getting started +-------------------------------- + +To install diff_classifier and begin analyzing your data, visit :ref: +`getting-started-label`. + +Particle tracking +----------------- + +For instructions scripting Trackmate for particle tracking analysis, visit +`Scripting Trackmate `_ as well as the +instructions using the diff_classifier pre-built functions +(:ref: `tracking-label`). + +Features analysis +----------------- + +Trajectory features calculations are based off the +`TrajClassifier `_ package developed by +Thorsten Wagner. The calculations can be found at the +`Traj wiki `_. +Instructions using the diff_classifier implementation can be found at +:ref: `features-analysis-label`. + +Interacting with s3 +------------------- + +Diff_classifier provides functions for interacting with buckets on AWS S3. +Instructions on implementing uploading to/downloading from s3 can be found at +:ref: `interacting-with-s3-label`. + +Cloudknot parallelization +------------------------- + +Diff_classifier includes `Cloudknot `_ +parallelization functions for complete tracking, analysis, and visualization +of large tracking experiments. In general, these are only templates, and will +have to be modified by the user for their own experimental implementations. +Instructions can be found at :ref: 'cloudknot-parallelization-label'. + +Usage +----- + +.. code-block:: python + + import diff_classifier.utils as ut + import diff_classifier.msd as msd + import diff_classifier.features as ft + import diff_classifier.imagej as ij + import diff_classifier.heatmaps as hm + + prefix = 'test_video' + frames = 651 + local_im = prefix + '.tif' # Name of image file + outfile = 'Traj' + local_im.split('.')[0] + '.csv' + msd_file = 'msd_{}.csv'.format(prefix) + ft_file = 'features_{}.csv'.format(prefix) + + ij.track(local_im, outfile, template=None, fiji_bin=None, radius=4.5, threshold=0., + do_median_filtering=True, quality=4.5, x=511, y=y, ylo=1, median_intensity=300.0, snr=0.0, + linking_max_distance=8.0, gap_closing_max_distance=10.0, max_frame_gap=2, + track_displacement=10.0) + + df = ut.csv_to_pd(outfile) + msds = msd.all_msds2(df, frames=frames) + features = ft.calculate_features(msds) + + hm.plot_trajectories(prefix) + +Bugs and issues +--------------- + +If you are having issues, please let us know by +`opening a new issue `_. +Please tag your issues with the "bug" or "question" label. + +License +------- + +This project is licensed under the +`MIT License `_. + +Acknowledgements +---------------- +The authors would like to thank the expertise and resources provided by the +eScience Institute at the University of Washington during their annual +Incubator Project that made diff_classifier possible. + +.. toctree:: + :maxdepth: 2 + :caption: User Documentation + + getting_started + example_data + tracking + features_analysis + interacting_with_s3 + cloudknot_parallelization + api/index + examples + code + bugs diff --git a/doc/source/interacting_with_S3.rst b/doc/source/interacting_with_S3.rst new file mode 100644 index 0000000..baa9aea --- /dev/null +++ b/doc/source/interacting_with_S3.rst @@ -0,0 +1,21 @@ +.. _interacting-with-s3-label: + +Uploading and download with s3 +============================== + +The two basic functions found in the aws package for interacting with s3 are +download_s3 and upload_s3. Users must select a bucket by changing the variable +bucket_name. Users must also have an AWS account and the correct permissions on +their computer to download from or upload to the bucket of interest. + +.. code-block:: python + + download_s3(remote_fname, local_fname, bucket_name="nancelab.publicfiles") + +Example tif images for tracking analysis are available in a publicly +accessible bucket nancelab.publicfiles. Despite the public access, users must +have an AWS account in order to access the files. + +For information on using the AWS Command Line Interface, check out the +documentation +`here `_. diff --git a/doc/source/tracking.rst b/doc/source/tracking.rst new file mode 100644 index 0000000..991255f --- /dev/null +++ b/doc/source/tracking.rst @@ -0,0 +1,93 @@ +.. _tracking-label: + +Tracking particles +================== + +The "track" function in diff_classifier operates by creating a temporary script +files in the TEMP directory and generating a command sent through the shell +to ImageJ. Tracking is implemented using the TrackMate ImageJ plugin. User +inputs include tracking parameters and spot and track filter cutoffs. + +This implementation is fairly constrained from the complete number of parameters +that Trackmate offers, but will be expanded in the future. The parameters that +are currently implemented are: + +* radius: estimated radius of spots in videos. In general, should be slightly + larger than the average particle size in the videos. Note that the GUI + interface uses diameter rather than radius. +* threshold: not currently in use. +* do_median_filtering: If set to True, filters the image before performing + tracking to minimize noise. +* quality: Lower threshold on spot quality filter. Usually varies anywhere between + 1 to 300. +* x: Upper threshold on x coordinate spot filter. +* y: Upper threshold on y coordinate spot filter. +* ylo: Lower threshold on y coordinate spot filter. +* linking_max_distance: max distance in pixels that a particle can travel between + frames. +* gap_closing_max_distance: max distance in pixels that a particle can travel when + it skips a frame. +* track_displacement: meant to be duration. Minimum number of frames a trajectory + must have to be included in the final dataset. + +The algorithm is set to only implement the difference of Gaussians detection +algorithm and the simple LAP tracker algorithm. + +.. code-block:: python + + track(target, out_file, template=None, fiji_bin=None, radius=2.5, threshold=5., + do_median_filtering=False, quality=30.0, x=511, y=511, ylo=1, + median_intensity=55000.0, snr=0.0, linking_max_distance=10.0, + gap_closing_max_distance=10.0, max_frame_gap=3, + track_displacement=0.0) + +Selecting trajectory parameters +------------------------------- + +One difficulty with particle tracking technology is that there is no easy way to +determine the "correct" tracking parameters for different videos. Even videos +collected in the same conditions using the same particles can have different +optimal tracking parameters. Parameters can also vary from user to user. How +does one select optimal parameters, especially where tracking large numbers of +videos? + +If users choose to use a single set of parameters for all videos, users risk +outputting poor tracking results, especially if there is uneven illumination +or different particle distributions with the video frame. This works well for +relatively homogeneous videos with similar particle sizes and uniform +illumination. + +I have implemented an intermediate approach in diff_classifier that uses +intensity data from input videos to predict quality cutoff parameters. This +assumes that particles in all videos have similar radii and are moving at +similar velocities, while adjusting the quality threshold filter. The function +imagej.regress_sys allows users to create a regression object that predicts +quality parameters from an input training dataset. Users must manually track +a small number of randomly selected videos from their entire video collection +and use the quality values as inputs to regress_sys e.g. + +.. code-block:: python + + prefix = 'test' + nvideos = 20 + all_videos = [] + for num in range(nvideos): + all_videos.append('{}_{}'.format(prefix, num)) + yfit = [] + training_size = 4 + + ij.regress_sys('.', all_videos, yfit, training_size, have_output=False) + +When have_output is set to False, regress_sys will output a list of randomly +selected videos contained in all_videos which the user must manually track. +The quality values are loaded into the variable yfit to produce the regression +object using scikit-learn: + +.. code-block:: python + + yfit = [20.4, 17.9, 10.3, 30.4] + regress = ij.regress_sys('.', all_videos, yfit, training_size, + have_output=False) + +An example of such an implementation can be found +`here `_. diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..fc57618 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +from setuptools import setup, find_packages +import setuptools.command.build_py +import os + +#from diff_classifier.version import LONG_DESCRIPTION_CONTENT_TYPE +# try: +# import fijibin +# except: +# print('Import error. Install Fiji manually.') + +ver_file = os.path.join('diff_classifier', 'version.py') +with open(ver_file) as f: + exec(f.read()) + +PACKAGES = find_packages() + +opts = dict(name=NAME, + maintainer=MAINTAINER, + maintainer_email=MAINTAINER_EMAIL, + description=DESCRIPTION, + long_description=LONG_DESCRIPTION, + long_description_content_type=LONG_DESCRIPTION_CONTENT_TYPE, + url=URL, + packages=PACKAGES, + download_url=DOWNLOAD_URL, + license=LICENSE, + classifiers=CLASSIFIERS, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + platforms=PLATFORMS, + version=VERSION, + install_requires=REQUIRES, + package_data=PACKAGE_DATA) + + +if __name__ == '__main__': + setup(**opts) + try: + #import fijibin + setup(cmdclass={'build_py': FijiCommand}) + except: + print('Import error. Install Fiji manually.')