diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..31fe9fc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' + +--- + +## Bug Report + +### Description + +A clear and concise description of what is the overall operation that is intended to be +performed that resulted in an error. + +### Reproducibility +Include: +- OS (WIN | MACOS | Linux) +- DataJoint Element Version +- MySQL Version +- MySQL Deployment Strategy (local-native | local-docker | remote) +- Minimum number of steps to reliably reproduce the issue +- Complete error stack as a result of evaluating the above steps + +### Expected Behavior +A clear and concise description of what you expected to happen. + +### Screenshots +If applicable, add screenshots to help explain your problem. + +### Additional Research and Context +Add any additional research or context that was conducted in creating this report. + +For example: +- Related GitHub issues and PR's either within this repository or in other relevant + repositories. +- Specific links to specific lines or a focus within source code. +- Relevant summary of Maintainers development meetings, milestones, projects, etc. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d31fbac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: DataJoint Contribution Guideline + url: https://docs.datajoint.org/python/community/02-Contribute.html + about: Please make sure to review the DataJoint Contribution Guidelines \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..1f2b784 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,57 @@ +--- +name: Feature request +about: Suggest an idea for a new feature +title: '' +labels: 'enhancement' +assignees: '' + +--- + +## Feature Request + +### Problem + +A clear and concise description how this idea has manifested and the context. Elaborate +on the need for this feature and/or what could be improved. Ex. I'm always frustrated +when [...] + +### Requirements + +A clear and concise description of the requirements to satisfy the new feature. Detail +what you expect from a successful implementation of the feature. Ex. When using this +feature, it should [...] + +### Justification + +Provide the key benefits in making this a supported feature. Ex. Adding support for this +feature would ensure [...] + +### Alternative Considerations + +Do you currently have a work-around for this? Provide any alternative solutions or +features you've considered. + +### Related Errors +Add any errors as a direct result of not exposing this feature. + +Please include steps to reproduce provided errors as follows: +- OS (WIN | MACOS | Linux) +- DataJoint Element Version +- MySQL Version +- MySQL Deployment Strategy (local-native | local-docker | remote) +- Minimum number of steps to reliably reproduce the issue +- Complete error stack as a result of evaluating the above steps + +### Screenshots +If applicable, add screenshots to help explain your feature. + +### Additional Research and Context +Add any additional research or context that was conducted in creating this feature request. + +For example: +- Related GitHub issues and PR's either within this repository or in other relevant + repositories. +- Specific links to specific lines or a focus within source code. +- Relevant summary of Maintainers development meetings, milestones, projects, etc. +- Any additional supplemental web references or links that would further justify this + feature request. diff --git a/.github/workflows/development.yaml b/.github/workflows/development.yaml new file mode 100644 index 0000000..549f126 --- /dev/null +++ b/.github/workflows/development.yaml @@ -0,0 +1,147 @@ +name: Development +on: + pull_request: + push: + tags: + - '*.*.*' +jobs: + test-changelog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get changelog entry + id: changelog_reader + uses: guzman-raphael/changelog-reader-action@v5 + with: + path: ./CHANGELOG.md + - name: Verify changelog parsing + env: + TAG_NAME: ${{steps.changelog_reader.outputs.version}} + RELEASE_NAME: Release ${{steps.changelog_reader.outputs.version}} + BODY: ${{steps.changelog_reader.outputs.changes}} + PRERELEASE: ${{steps.changelog_reader.outputs.status == 'prereleased'}} + DRAFT: ${{steps.changelog_reader.outputs.status == 'unreleased'}} + run: | + echo "TAG_NAME=${TAG_NAME}" + echo "RELEASE_NAME=${RELEASE_NAME}" + echo "BODY=${BODY}" + echo "PRERELEASE=${PRERELEASE}" + echo "DRAFT=${DRAFT}" + build: + needs: test-changelog + runs-on: ubuntu-latest + strategy: + matrix: + include: + - py_ver: 3.8 + distro: alpine + image: djbase + env: + PY_VER: ${{matrix.py_ver}} + DISTRO: ${{matrix.distro}} + IMAGE: ${{matrix.image}} + DOCKER_CLIENT_TIMEOUT: "120" + COMPOSE_HTTP_TIMEOUT: "120" + steps: + - uses: actions/checkout@v2 + - name: Compile image + run: | + export PKG_NAME=$(python3 -c "print([p for p in __import__('setuptools').find_packages() if '.' not in p][0])") + export PKG_VERSION=$(cat ${PKG_NAME}/version.py | awk -F\' '/__version__ = / {print $2}') + export HOST_UID=$(id -u) + docker-compose -f docker-compose-build.yaml up --exit-code-from element --build + IMAGE=$(docker images --filter "reference=datajoint/${PKG_NAME}*" \ + --format "{{.Repository}}") + TAG=$(docker images --filter "reference=datajoint/${PKG_NAME}*" --format "{{.Tag}}") + docker save "${IMAGE}:${TAG}" | \ + gzip > "image-${PKG_NAME}-${PKG_VERSION}-py${PY_VER}-${DISTRO}.tar.gz" + echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_ENV + echo "PKG_VERSION=${PKG_VERSION}" >> $GITHUB_ENV + - name: Add image artifact + uses: actions/upload-artifact@v2 + with: + name: image-${{env.PKG_NAME}}-${{env.PKG_VERSION}}-py${{matrix.py_ver}}-${{matrix.distro}} + path: "image-${{env.PKG_NAME}}-${{env.PKG_VERSION}}-py${{matrix.py_ver}}-\ + ${{matrix.distro}}.tar.gz" + retention-days: 1 + - if: matrix.py_ver == '3.8' && matrix.distro == 'alpine' + name: Add pip artifacts + uses: actions/upload-artifact@v2 + with: + name: pip-${{env.PKG_NAME}}-${{env.PKG_VERSION}} + path: dist + retention-days: 1 + publish-release: + if: github.event_name == 'push' + needs: build + runs-on: ubuntu-latest + env: + TWINE_USERNAME: ${{secrets.twine_username}} + TWINE_PASSWORD: ${{secrets.twine_password}} + outputs: + release_upload_url: ${{steps.create_gh_release.outputs.upload_url}} + steps: + - uses: actions/checkout@v2 + - name: Determine package version + run: | + PKG_NAME=$(python3 -c "print([p for p in __import__('setuptools').find_packages() if '.' not in p][0])") + SDIST_PKG_NAME=$(echo ${PKG_NAME} | sed 's|_|-|g') + PKG_VERSION=$(cat ${PKG_NAME}/version.py | awk -F\' '/__version__ = / {print $2}') + echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_ENV + echo "PKG_VERSION=${PKG_VERSION}" >> $GITHUB_ENV + echo "SDIST_PKG_NAME=${SDIST_PKG_NAME}" >> $GITHUB_ENV + - name: Get changelog entry + id: changelog_reader + uses: guzman-raphael/changelog-reader-action@v5 + with: + path: ./CHANGELOG.md + version: ${{env.PKG_VERSION}} + - name: Create GH release + id: create_gh_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + tag_name: ${{steps.changelog_reader.outputs.version}} + release_name: Release ${{steps.changelog_reader.outputs.version}} + body: ${{steps.changelog_reader.outputs.changes}} + prerelease: ${{steps.changelog_reader.outputs.status == 'prereleased'}} + draft: ${{steps.changelog_reader.outputs.status == 'unreleased'}} + - name: Fetch image artifact + uses: actions/download-artifact@v2 + with: + name: image-${{env.PKG_NAME}}-${{env.PKG_VERSION}}-py3.8-alpine + - name: Fetch pip artifacts + uses: actions/download-artifact@v2 + with: + name: pip-${{env.PKG_NAME}}-${{env.PKG_VERSION}} + path: dist + - name: Publish pip release + run: | + export HOST_UID=$(id -u) + docker load < "image-${{env.PKG_NAME}}-${PKG_VERSION}-py3.8-alpine.tar.gz" + docker-compose -f docker-compose-build.yaml run \ + -e TWINE_USERNAME=${TWINE_USERNAME} -e TWINE_PASSWORD=${TWINE_PASSWORD} element \ + sh -lc "pip install twine && python -m twine upload dist/*" + - name: Determine pip artifact paths + run: | + echo "PKG_WHEEL_PATH=$(ls dist/${PKG_NAME}-*.whl)" >> $GITHUB_ENV + echo "PKG_SDIST_PATH=$(ls dist/${SDIST_PKG_NAME}-*.tar.gz)" >> $GITHUB_ENV + - name: Upload pip wheel asset to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + upload_url: ${{steps.create_gh_release.outputs.upload_url}} + asset_path: ${{env.PKG_WHEEL_PATH}} + asset_name: pip-${{env.PKG_NAME}}-${{env.PKG_VERSION}}.whl + asset_content_type: application/zip + - name: Upload pip sdist asset to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + upload_url: ${{steps.create_gh_release.outputs.upload_url}} + asset_path: ${{env.PKG_SDIST_PATH}} + asset_name: pip-${{env.SDIST_PKG_NAME}}-${{env.PKG_VERSION}}.tar.gz + asset_content_type: application/gzip diff --git a/Background.md b/Background.md deleted file mode 100644 index c1895d1..0000000 --- a/Background.md +++ /dev/null @@ -1,81 +0,0 @@ -<div style="text-align: justify"> - -# Miniscope Element - -## Description of modality, user population - -Miniature fluorescence microscopes (miniscopes) are a head-mounted calcium imaging full-frame video modality first introduced in 2005 by Mark Schnitzer's lab ([Flusberg et al., Optics Letters 2005](https://pubmed.ncbi.nlm.nih.gov/16190441/)). Due to their light weight, these miniscopes allow measuring the dynamic activity of populations of cortical neurons in freely behaving animals. In 2011, Inscopix Inc. was founded to support one-photon miniscopes as a commercial neuroscience research platform, providing proprietary hardware, acquisition software, and analysis software. Today, they estimate their active user base is 491 labs with a total of 1179 installs. An open-source alternative was launched by a UCLA team led by Daniel Aharoni and Peyman Golshani ([Cai et al., Nature 2016](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5063500/); [Aharoni and Hoogland, Frontiers in Cellular Neuroscience 2019](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6461004/)). In our conversation with Dr. Aharoni, he estimated about 700 labs currently using the UCLA system alone. The Inscopix user base is smaller but more established. Several two-photon miniscopes have been developed but lack widespread adoption likely due to the expensive hardware required for the two-photon excitation ([Helmchen et al., Neuron 2001](https://pubmed.ncbi.nlm.nih.gov/11580892/); [Zong et al., Nature Methods 2017](https://pubmed.ncbi.nlm.nih.gov/28553965/); [Aharoni and Hoogland, Frontiers in Cellular Neuroscience 2019](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6461004/)). Due to the low costs and ability to record during natural behaviors, one-photon miniscope imaging appears to be the fastest growing calcium imaging modality in the field today. In Year 1, we focused our efforts on supporting the UCLA platform due its fast growth and deficiency of standardization in acquisition and processing pipelines. In future phases, we will reach out to Inscopix to support their platform as well. - - -## Acquisition tools -Daniel Aharoni's lab has developed iterations of the UCLA Miniscope platform. Based on interviews, we have found labs using the two most recent versions including [Miniscope DAQ V3](http://miniscope.org/index.php/Information_on_the_(previous_Version_3)_Miniscope_platform) and [Miniscope DAQ V4](https://github.com/Aharoni-Lab/Miniscope-v4/wiki). Labs also use the Bonsai OpenEphys tool for data acquisition with the UCLA miniscope. Inscopix provides the Inscopix Data Acquisition Software (IDAS) for the nVista and nVoke systems. - -## Preprocessing tools -The preprocessing workflow for miniscope imaging includes denoising, motion correction, cell segmentation, and calcium event extraction (sometimes described as "deconvolution" or "spike inference"). For the UCLA Miniscopes, the following [analysis packages](https://github.com/Aharoni-Lab/Miniscope-v4/wiki/Analysis-Packages) are commonly used: - -(Package, Developer [Affiliation], Programming Language) - -+ [Miniscope Denoising](https://github.com/Aharoni-Lab/Miniscope-v4/wiki/Removing-Horizontal-Noise-from-Recordings), Daniel Aharoni (UCLA), Python -+ [NoRMCorre](https://github.com/flatironinstitute/NoRMCorre), Flatiron Institute, MATLAB -+ [CNMF-E](https://github.com/zhoupc/CNMF_E), Pengcheng Zhou (Liam Paninski’s Lab, Columbia University), MATLAB -+ [CaImAn](https://github.com/flatironinstitute/CaImAn), Flatiron Institute, Python -+ [miniscoPy](https://github.com/PeyracheLab/miniscoPy), Guillaume Viejo (Adrien Peyrache’s Lab, McGill University), Python -+ [MIN1PIPE](https://github.com/JinghaoLu/MIN1PIPE), Jinghao Lu (Fan Wang’s Lab, MIT), MATLAB -+ [CIAtah](https://github.com/bahanonu/calciumImagingAnalysis), Biafra Ahanonu, MATLAB -+ [MiniAn](https://github.com/DeniseCaiLab/minian), Phil Dong (Denise Cai's Lab, Mount Sinai), Python -+ [MiniscopeAnalysis](https://github.com/etterguillaume/MiniscopeAnalysis), Guillaume Etter (Sylvain Williams’ Lab, McGill University), MATLAB -+ [PIMPN](https://github.com/etterguillaume/PIMPN), Guillaume Etter (Sylvain Williams’ Lab, McGill University), Python -+ [CellReg](https://github.com/zivlab/CellReg), Liron Sheintuch (Yaniv Ziv’s Lab, Weizmann Institute of Science), MATLAB -+ Inscopix Data Processing Software (IDPS) -+ Inscopix Multimodal Image Registration and Analysis (MIRA) - -Based on interviews with UCLA and Inscopix miniscope users and developers, each research lab uses a different preprocessing workflow. These custom workflows are often closed source and not tracked with version control software. For the preprocessing tools that are open source, they are often developed by an individual during their training period and lack funding for long term maintenance. These factors result in a lack of standardization for miniscope preprocessing tools, which is a major obstacle to adoption for new labs. - - -## Precursor projects and interviews -Until recently, DataJoint had not been used for miniscope pipelines. However, labs we have contacted have been eager to engage and adopt DataJoint-based workflows in their labs. - -(Date, Person [Role], Lab, Institution) - -+ March 16, 2021, Niccoló Calcini (Postdoctoral scholar) & Antoine Adamantidis (Associate Professor), Antoine Adamantidis’ Lab, University of Bern -+ March 12, 2021, Biafra Ahanonu (Postdoctoral scholar), Allan Basbaum’s Lab, UCSF -+ March 1, 2021, Lukas Oesch (Postdoctoral scholar), Anne Churchland’s Lab, UCLA -+ February 25, 2021, Manolis Froudarakis (Assistant Professor), Manolis Froudarakis’ Lab, FORTH -+ February 22, 2021, Jinghao Lu (Doctoral student) & Vincent Prevosto (Research scientist), Fan Wang’s Lab, MIT -+ February 12, 2021, Guillaume Viejo (Postdoctoral scholar) & Adrien Peyrache (Assistant Professor), Adrien Peyrache’s Lab, McGill University -+ February 11, 2021, Daniel Aharoni (Assistant Professor), Daniel Aharoni’s Lab, UCLA -+ January 29, 2021, Pingping Zhao & Ronen Reshef (Postdoctoral scholars), Peyman Golshani’s Lab, UCLA - -## Pipeline Development - -With assistance from Peyman Golshani’s Lab (UCLA) we have added support for the UCLA Miniscope DAQ V3 acquisition tool and MiniscopeAnalysis preprocessing tool in `element-miniscope` and `workflow-miniscope`. They have provided example data for development, and will begin validating in March 2021. - -Based on interviews, we are considering adding support for the tools listed below. The deciding factors include the number of users, long term support, quality controls, and python programming language (so that the preprocessing tool can be triggered within the element). - -+ Acquisition tools - + Miniscope DAQ V4 - + Inscopix Data Acquisition Software (IDAS) -+ Preprocessing tools - + Inscopix Data Processing Software (IDPS) - + Inscopix Multimodal Image Registration and Analysis (MIRA) - + MiniAn - + CaImAn - + CNMF-E - + CellReg - -## Alpha release: Validation sites - -We have recruited the following teams as alpha users to jointly develop and validate the miniscope workflow in their experiments. - -(Lab [PI], Institution, Hosting arrangement, Start time, Experiment 0 target) - -+ Peyman Golshani, UCLA, djHub, January 2021, March 2021 -+ Anne Churchland, UCLA, Lab server, April 2021, May 2021 -+ Fan Wang, MIT, djHub, April 2021, May 2021 -+ Antoine Adamantidis, University of Bern, djHub, April 2021, June 2021 -+ Manolis Froudarakis, FORTH, Lab server, April 2021, June 2021 - -## Beta release -As the validation progresses, we expect to produce a beta version of the workflow for users to adopt independently by May 1, 2021. - -</div> \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..912de5b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention. + +## Unreleased - 2022-04-25 ++ Add - Load data acquired with Miniscope-DAQ-V4 ++ Add - Load data analyzed with CaImAn ++ Add - Trigger CaImAn analysis ++ Remove - Load data analyzed with MiniscopeAnalysis + +## 0.1.1 - 2021-04-01 ++ Add - Load data acquired with Miniscope-DAQ-V3 ++ Add - Load data analyzed with MiniscopeAnalysis \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c144a40 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +ARG PY_VER +ARG DISTRO +ARG IMAGE +ARG PKG_NAME +ARG PKG_VERSION + +FROM datajoint/${IMAGE}:py${PY_VER}-${DISTRO} +COPY --chown=anaconda:anaconda ./requirements.txt ./setup.py \ + /main/ +COPY --chown=anaconda:anaconda ./${PKG_NAME} /main/${PKG_NAME} +RUN \ + cd /main && \ + pip install . && \ + rm -R /main/* +WORKDIR /main diff --git a/LICENSE b/LICENSE index d394fe3..2f92789 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 DataJoint NEURO +Copyright (c) 2022 DataJoint Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b84bd9e..bd95a9b 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ # DataJoint Element - Miniscope Calcium Imaging -+ This repository features DataJoint pipeline design for functional calcium imaging, -with `Miniscope DAQ V3` acquisition system and `MiniscopeAnalysis` suite for analysis. ++ This repository features a DataJoint pipeline design for functional calcium imaging +data acquired with the UCLA Miniscope and `Miniscope DAQ V4` acquisition system, and +analyzed with `CaImAn`. + The element presented here is not a complete workflow by itself, - but rather a modular design of tables and dependencies specific to the functional calcium imaging workflow. + but rather a modular design of tables and dependencies specific to the miniscope + functional calcium imaging workflow. -+ This modular element can be flexibly attached downstream to -any particular design of experiment session, thus assembling -a fully functional calcium imaging workflow. ++ This modular element can be flexibly attached downstream to any particular design of +experiment session, thus assembling a fully functional miniscope workflow. -+ See [Background](Background.md) for the background information and development timeline. ++ See the [Element Miniscope documentation]( + https://elements.datajoint.org/description/miniscope/) for the background + information and development timeline. -+ See [DataJoint Elements](https://github.com/datajoint/datajoint-elements) for descriptions of the other `elements` and `workflows` developed as part of this initiative. - -## Element usage - -+ See [workflow-miniscope](https://github.com/datajoint/workflow-miniscope) -repository for an example usage of `element-miniscope`. ++ For more information on the DataJoint Elements project, please visit +https://elements.datajoint.org. This work is supported by the National Institutes of +Health. ## Element architecture @@ -25,47 +25,151 @@ repository for an example usage of `element-miniscope`. + As the diagram depicts, `elements-miniscope` starts immediately downstream from `Session`, and also requires some notion of: - + `Scanner` for equipment/device + + `Equipment` for equipment/device - + `Location` as a dependency for `ScanLocation` + + `AnatomicalLocation` as a dependency for `RecordingLocation` -### Scan +## Table definitions -+ A `Session` (more specifically an experimental session) may have multiple scans, where each scan describes a complete 4D dataset (i.e. 3D volume over time) from one scanning session, typically from the moment of pressing the *start* button to pressing the *stop* button. +The `miniscope` schema contains all tables in this Element, including for the +acquired metadata and analysis results. -+ `Scan` - table containing information about the equipment used (e.g. the Scanner information) +### Recording -+ `ScanInfo` - meta information about this scan, from ScanImage header (e.g. frame rate, number of channels, scanning depths, frames, etc.) +<details> +<summary>Click to expand details</summary> -+ `ScanInfo.Field` - a field is a 2D image at a particular xy-coordinate and plane (scanning depth) within the field-of-view (FOV) of the scan. ++ A `Session` (more specifically an experimental session) may have multiple recordings, +where each recording describes a complete 3D dataset (i.e. 2d image over time) from one +recording session, typically from the moment of pressing the start button to pressing +the stop button. - + For resonant scanner, a field is usually the 2D image occupying the entire FOV from a certain plane (at some depth). ++ `Recording` - table containing information about the equipment used +(e.g. the acquisition hardware information) - + For mesoscope scanner, with much wider FOV, there may be multiple fields on one plane. ++ `RecordingInfo` - metadata about this recording from the Miniscope DAQ software +(e.g. frame rate, number of channels, frames, etc.) + +</details> ### Motion correction -+ `MotionCorrection` - motion correction information performed on a scan +<details> +<summary>Click to expand details</summary> + ++ `MotionCorrection` - motion correction information performed on a recording -+ `MotionCorrection.RigidMotionCorrection` - details of the rigid motion correction (e.g. shifting in x, y) at a per `ScanInfo.Field` level ++ `MotionCorrection.RigidMotionCorrection` - details of the rigid motion correction +(e.g. shifting in x, y) -+ `MotionCorrection.NonRigidMotionCorrection` and `MotionCorrection.Block` tables are used to describe the non-rigid motion correction performed on each `ScanInfo.Field` ++ `MotionCorrection.NonRigidMotionCorrection` and `MotionCorrection.Block` tables are +used to describe the non-rigid motion correction. + ++ `MotionCorrection.Summary` - summary images after motion correction +(e.g. average image, correlation image, etc.) + +</details> -+ `MotionCorrection.Summary` - summary images for each `ScanInfo.Field` after motion correction (e.g. average image, correlation image) - ### Segmentation -+ `Segmentation` - table specifies the segmentation step and its outputs, following the motion correction step. - -+ `Segmentation.Mask` - image mask for the segmented region of interest from a particular `ScanInfo.Field` +<details> +<summary>Click to expand details</summary> + ++ `Segmentation` - table specifies the segmentation step and its outputs, following the + motion correction step. + ++ `Segmentation.Mask` - image mask for the segmented region of interest + ++ `MaskClassification` - classification of `Segmentation.Mask` into a type + (e.g. soma, axon, dendrite, artifact, etc.) -+ `MaskClassification` - classification of `Segmentation.Mask` into different type (e.g. soma, axon, dendrite, artifact, etc.) +</details> ### Neural activity extraction +<details> +<summary>Click to expand details</summary> + + `Fluorescence` - fluorescence traces extracted from each `Segmentation.Mask` -+ `ActivityExtractionMethod` - activity extraction method (e.g. deconvolution) to be applied on fluorescence trace ++ `ActivityExtractionMethod` - activity extraction method (e.g. deconvolution) applied + on the fluorescence trace + `Activity` - computed neuronal activity trace from fluorescence trace (e.g. spikes) +</details> + +## Installation + ++ The installation instructions can be found at the +[DataJoint Elements documentation](https://elements.datajoint.org/usage/install/). + ++ Install `element-miniscope` + ``` + pip install element-miniscope + ``` + ++ Install `element-interface` + + + `element-interface` contains data loading utilities for `element-miniscope`. + + + `element-interface` is a dependency of `element-miniscope`, however it is not contained within `requirements.txt`, therefore, must be installed in addition to the installation of the `element-miniscope`. + ```bash + pip install "element-interface @ git+https://github.com/datajoint/element-interface" + ``` + + + `element-interface` can also be used to install packages used for reading acquired data and running analysis (e.g. `CaImAn`). + + + If your workflow uses these packages, you should install them when you install `element-interface`. + +## Usage + +### Element activation + +When using this Element, one needs to run `miniscope.activate` to declare the schemas +and tables on the database. + +<details> +<summary>Click to expand details</summary> + +To activate `element-miniscope`, ones need to provide: + +1. Schema names + + schema name for the miniscope module + +2. Upstream tables + + Session table: A set of keys identifying a recording session (see [Element-Session](https://github.com/datajoint/element-session)). + + Equipment table: A reference table for Recording, specifying the equipment used for the acquisition (see [example pipeline](https://github.com/datajoint/workflow-miniscope/blob/main/workflow_miniscope/pipeline.py)). + + AnatomicalLocation table: A reference table for RecordingLocation, specifying + the brain location where the recording is acquired + +3. Utility functions. See [example definitions here](https://github.com/datajoint/workflow-miniscope/blob/main/workflow_miniscope/paths.py). + + get_miniscope_root_data_dir(): Returns your root data directory. + + get_session_directory(): Returns the path of the session data relative to the + root directory. + +For more details, check the docstring of `element-miniscope`: +```python + help(miniscope.activate) +``` + +</details> + +### Element usage + ++ See the [workflow-miniscope](https://github.com/datajoint/workflow-miniscope) +repository for an example usage of `element-miniscope`. + +## Citation + ++ If your work uses DataJoint and DataJoint Elements, please cite the respective Research Resource Identifiers (RRIDs) and manuscripts. + ++ DataJoint for Python or MATLAB + + Yatsenko D, Reimer J, Ecker AS, Walker EY, Sinz F, Berens P, Hoenselaar A, Cotton RJ, Siapas AS, Tolias AS. DataJoint: managing big scientific data using MATLAB or Python. bioRxiv. 2015 Jan 1:031658. doi: https://doi.org/10.1101/031658 + + + DataJoint ([RRID:SCR_014543](https://scicrunch.org/resolver/SCR_014543)) - DataJoint for `<Select Python or MATLAB >` (version `<Enter version number>`) + ++ DataJoint Elements + + Yatsenko D, Nguyen T, Shen S, Gunalan K, Turner CA, Guzman R, Sasaki M, Sitonic D, Reimer J, Walker EY, Tolias AS. DataJoint Elements: Data Workflows for Neurophysiology. bioRxiv. 2021 Jan 1. doi: https://doi.org/10.1101/2021.03.30.437358 + + + DataJoint Elements ([RRID:SCR_021894](https://scicrunch.org/resolver/SCR_021894)) - Element Miniscope (version `<Enter version number>`) \ No newline at end of file diff --git a/docker-compose-build.yaml b/docker-compose-build.yaml new file mode 100644 index 0000000..81984c7 --- /dev/null +++ b/docker-compose-build.yaml @@ -0,0 +1,26 @@ +# PY_VER=3.8 IMAGE=djbase DISTRO=alpine PKG_NAME=$(python -c "print([p for p in __import__('setuptools').find_packages() if '.' not in p][0])") PKG_VERSION=$(cat ${PKG_NAME}/version.py | awk -F\' '/__version__/ {print $2}') HOST_UID=$(id -u) docker-compose -f docker-compose-build.yaml up --exit-code-from element --build +# +# Intended for updating dependencies and docker image. +# Used to build release artifacts. +version: "2.4" +services: + element: + build: + context: . + args: + - PY_VER + - DISTRO + - IMAGE + - PKG_NAME + - PKG_VERSION + image: datajoint/${PKG_NAME}:${PKG_VERSION} + user: ${HOST_UID}:anaconda + volumes: + - .:/main + command: + - sh + - -lc + - | + set -e + rm -R build dist *.egg-info || echo "No prev build" + python setup.py bdist_wheel sdist diff --git a/element_miniscope/imaging.py b/element_miniscope/imaging.py deleted file mode 100644 index f3360a9..0000000 --- a/element_miniscope/imaging.py +++ /dev/null @@ -1,719 +0,0 @@ -import datajoint as dj -import numpy as np -import pathlib -from datetime import datetime -import uuid -import hashlib -import importlib -import inspect - -from . import scan - -schema = dj.schema() - -_linking_module = None - - -def activate(imaging_schema_name, scan_schema_name=None, *, - create_schema=True, create_tables=True, linking_module=None): - """ - activate(imaging_schema_name, *, scan_schema_name=None, create_schema=True, create_tables=True, linking_module=None) - :param imaging_schema_name: schema name on the database server to activate the `imaging` module - :param scan_schema_name: schema name on the database server to activate the `scan` module - - may be omitted if the `scan` module is already activated - :param create_schema: when True (default), create schema in the database if it does not yet exist. - :param create_tables: when True (default), create tables in the database if they do not yet exist. - :param linking_module: a module name or a module containing the - required dependencies to activate the `imaging` module: - Upstream tables: - + Session: parent table to Scan, typically identifying a recording session - Functions: - + get_imaging_root_data_dir() -> str - Retrieve the root data directory - e.g. containing all subject/sessions data - :return: a string for full path to the root data directory - """ - - if isinstance(linking_module, str): - linking_module = importlib.import_module(linking_module) - assert inspect.ismodule(linking_module),\ - "The argument 'dependency' must be a module's name or a module" - - global _linking_module - _linking_module = linking_module - - scan.activate(scan_schema_name, create_schema=create_schema, - create_tables=create_tables, linking_module=linking_module) - schema.activate(imaging_schema_name, create_schema=create_schema, - create_tables=create_tables, add_objects=_linking_module.__dict__) - - -# -------------- Functions required by the element-calcium-imaging -------------- - -def get_imaging_root_data_dir() -> str: - """ - get_imaging_root_data_dir() -> str - Retrieve the root data directory - e.g. containing all subject/sessions data - :return: a string for full path to the root data directory - """ - return _linking_module.get_imaging_root_data_dir() - - -# -------------- Table declarations -------------- - - -@schema -class ProcessingMethod(dj.Lookup): - definition = """ - processing_method: char(32) - --- - processing_method_desc: varchar(1000) - """ - - contents = [('caiman', 'CaImAn Analysis Suite'), - ('mcgill_miniscope_analysis', 'MiniscopeAnalysis from McGill University (https://github.com/etterguillaume/MiniscopeAnalysis)')] - - -@schema -class ProcessingParamSet(dj.Lookup): - definition = """ - paramset_idx: smallint - --- - -> ProcessingMethod - paramset_desc: varchar(128) - param_set_hash: uuid - unique index (param_set_hash) - params: longblob # dictionary of all applicable parameters - """ - - @classmethod - def insert_new_params(cls, processing_method: str, - paramset_idx: int, paramset_desc: str, params: dict): - param_dict = {'processing_method': processing_method, - 'paramset_idx': paramset_idx, - 'paramset_desc': paramset_desc, - 'params': params, - 'param_set_hash': dict_to_uuid(params)} - q_param = cls & {'param_set_hash': param_dict['param_set_hash']} - - if q_param: # If the specified param-set already exists - pname = q_param.fetch1('paramset_idx') - if pname == paramset_idx: # If the existed set has the same name: job done - return - else: # If not same name: human error, trying to add the same paramset with different name - raise dj.DataJointError( - 'The specified param-set already exists - name: {}'.format(pname)) - else: - cls.insert1(param_dict) - - -@schema -class CellCompartment(dj.Lookup): - definition = """ # cell compartments that can be imaged - cell_compartment : char(16) - """ - - contents = zip(['axon', 'soma', 'bouton']) - - -@schema -class MaskType(dj.Lookup): - definition = """ # possible classifications for a segmented mask - mask_type : varchar(16) - """ - - contents = zip(['soma', 'axon', 'dendrite', 'neuropil', 'artefact', 'unknown']) - - -# -------------- Trigger a processing routine -------------- - -@schema -class ProcessingTask(dj.Manual): - definition = """ - -> scan.Scan - -> ProcessingParamSet - --- - processing_output_dir: varchar(255) # output directory of the processed scan relative to root data directory - task_mode='load': enum('load', 'trigger') # 'load': load computed analysis results, 'trigger': trigger computation - """ - - -@schema -class Processing(dj.Computed): - definition = """ - -> ProcessingTask - --- - processing_time : datetime # time of generation of this set of processed, segmented results - package_version='' : varchar(16) - """ - - # Run processing only on Scan with ScanInfo inserted - @property - def key_source(self): - return ProcessingTask & scan.ScanInfo - - def make(self, key): - task_mode = (ProcessingTask & key).fetch1('task_mode') - method, loaded_result = get_loader_result(key, ProcessingTask) - - if task_mode == 'load': - if method == 'caiman': - loaded_caiman = loaded_result - key = {**key, 'processing_time': loaded_caiman.creation_time} - elif method == 'mcgill_miniscope_analysis': - loaded_miniscope_analysis = loaded_result - key = {**key, 'processing_time': loaded_miniscope_analysis.creation_time} - else: - raise NotImplementedError('Unknown method: {}'.format(method)) - elif task_mode == 'trigger': - raise NotImplementedError(f'Automatic triggering of {method} analysis' - f' is not yet supported') - else: - raise ValueError(f'Unknown task mode: {task_mode}') - - self.insert1(key) - - -@schema -class Curation(dj.Manual): - definition = """ - -> Processing - curation_id: int - --- - curation_time: datetime # time of generation of this set of curated results - curation_output_dir: varchar(255) # output directory of the curated results, relative to root data directory - manual_curation: bool # has manual curation been performed on this result? - curation_note='': varchar(2000) - """ - - def create1_from_processing_task(self, key, is_curated=False, curation_note=''): - """ - A convenient function to create a new corresponding "Curation" for a particular "ProcessingTask" - """ - if key not in Processing(): - raise ValueError(f'No corresponding entry in Processing available for: {key};' - f' do `Processing.populate(key)`') - - output_dir = (ProcessingTask & key).fetch1('processing_output_dir') - method, loaded_result = get_loader_result(key, ProcessingTask) - - if method == 'caiman': - loaded_caiman = loaded_result - curation_time = loaded_caiman.creation_time - elif method == 'mcgill_miniscope_analysis': - loaded_miniscope_analysis = loaded_result - curation_time = loaded_miniscope_analysis.creation_time - else: - raise NotImplementedError('Unknown method: {}'.format(method)) - - # Synthesize curation_id - curation_id = dj.U().aggr(self & key, n='ifnull(max(curation_id)+1,1)').fetch1('n') - self.insert1({**key, 'curation_id': curation_id, - 'curation_time': curation_time, 'curation_output_dir': output_dir, - 'manual_curation': is_curated, - 'curation_note': curation_note}) - - -# -------------- Motion Correction -------------- - -@schema -class MotionCorrection(dj.Imported): - definition = """ - -> Processing - --- - -> scan.Channel.proj(motion_correct_channel='channel') # channel used for motion correction in this processing task - """ - - class RigidMotionCorrection(dj.Part): - definition = """ - -> master - --- - outlier_frames=null : longblob # mask with true for frames with outlier shifts (already corrected) - y_shifts : longblob # (pixels) y motion correction shifts - x_shifts : longblob # (pixels) x motion correction shifts - z_shifts=null : longblob # (pixels) z motion correction shifts (z-drift) - y_std : float # (pixels) standard deviation of y shifts across all frames - x_std : float # (pixels) standard deviation of x shifts across all frames - z_std=null : float # (pixels) standard deviation of z shifts across all frames - """ - - class NonRigidMotionCorrection(dj.Part): - """ - Piece-wise rigid motion correction - - tile the FOV into multiple 3D blocks/patches - """ - definition = """ - -> master - --- - outlier_frames=null : longblob # mask with true for frames with outlier shifts (already corrected) - block_height : int # (pixels) - block_width : int # (pixels) - block_depth : int # (pixels) - block_count_y : int # number of blocks tiled in the y direction - block_count_x : int # number of blocks tiled in the x direction - block_count_z : int # number of blocks tiled in the z direction - """ - - class Block(dj.Part): - definition = """ # FOV-tiled blocks used for non-rigid motion correction - -> master.NonRigidMotionCorrection - block_id : int - --- - block_y : longblob # (y_start, y_end) in pixel of this block - block_x : longblob # (x_start, x_end) in pixel of this block - block_z : longblob # (z_start, z_end) in pixel of this block - y_shifts : longblob # (pixels) y motion correction shifts for every frame - x_shifts : longblob # (pixels) x motion correction shifts for every frame - z_shifts=null : longblob # (pixels) x motion correction shifts for every frame - y_std : float # (pixels) standard deviation of y shifts across all frames - x_std : float # (pixels) standard deviation of x shifts across all frames - z_std=null : float # (pixels) standard deviation of z shifts across all frames - """ - - class Summary(dj.Part): - definition = """ # summary images for each field and channel after corrections - -> master - -> scan.ScanInfo.Field - --- - ref_image=null : longblob # image used as alignment template - average_image : longblob # mean of registered frames - correlation_image=null : longblob # correlation map (computed during cell detection) - max_proj_image=null : longblob # max of registered frames - """ - - def make(self, key): - method, loaded_result = get_loader_result(key, ProcessingTask) - - if method == 'caiman': - loaded_caiman = loaded_result - - self.insert1({**key, 'motion_correct_channel': loaded_caiman.alignment_channel}) - - is3D = loaded_caiman.params.motion['is3D'] - # -- rigid motion correction -- - if not loaded_caiman.params.motion['pw_rigid']: - rigid_correction = { - **key, - 'x_shifts': loaded_caiman.motion_correction['shifts_rig'][:, 0], - 'y_shifts': loaded_caiman.motion_correction['shifts_rig'][:, 1], - 'z_shifts': (loaded_caiman.motion_correction['shifts_rig'][:, 2] - if is3D - else np.full_like( - loaded_caiman.motion_correction['shifts_rig'][:, 0], 0)), - 'x_std': np.nanstd(loaded_caiman.motion_correction['shifts_rig'][:, 0]), - 'y_std': np.nanstd(loaded_caiman.motion_correction['shifts_rig'][:, 1]), - 'z_std': (np.nanstd(loaded_caiman.motion_correction['shifts_rig'][:, 2]) - if is3D - else np.nan), - 'outlier_frames': None} - - self.RigidMotionCorrection.insert1(rigid_correction) - - # -- non-rigid motion correction -- - else: - nonrigid_correction = { - **key, - 'block_height': (loaded_caiman.params.motion['strides'][0] - + loaded_caiman.params.motion['overlaps'][0]), - 'block_width': (loaded_caiman.params.motion['strides'][1] - + loaded_caiman.params.motion['overlaps'][1]), - 'block_depth': (loaded_caiman.params.motion['strides'][2] - + loaded_caiman.params.motion['overlaps'][2] - if is3D else 1), - 'block_count_x': len( - set(loaded_caiman.motion_correction['coord_shifts_els'][:, 0])), - 'block_count_y': len( - set(loaded_caiman.motion_correction['coord_shifts_els'][:, 2])), - 'block_count_z': (len( - set(loaded_caiman.motion_correction['coord_shifts_els'][:, 4])) - if is3D else 1), - 'outlier_frames': None} - - nonrigid_blocks = [] - for b_id in range(len(loaded_caiman.motion_correction['x_shifts_els'][0, :])): - nonrigid_blocks.append( - {**key, 'block_id': b_id, - 'block_x': np.arange(*loaded_caiman.motion_correction[ - 'coord_shifts_els'][b_id, 0:2]), - 'block_y': np.arange(*loaded_caiman.motion_correction[ - 'coord_shifts_els'][b_id, 2:4]), - 'block_z': (np.arange(*loaded_caiman.motion_correction[ - 'coord_shifts_els'][b_id, 4:6]) - if is3D - else np.full_like( - np.arange(*loaded_caiman.motion_correction[ - 'coord_shifts_els'][b_id, 0:2]), 0)), - 'x_shifts': loaded_caiman.motion_correction[ - 'x_shifts_els'][:, b_id], - 'y_shifts': loaded_caiman.motion_correction[ - 'y_shifts_els'][:, b_id], - 'z_shifts': (loaded_caiman.motion_correction[ - 'z_shifts_els'][:, b_id] - if is3D - else np.full_like( - loaded_caiman.motion_correction['x_shifts_els'][:, b_id], 0)), - 'x_std': np.nanstd(loaded_caiman.motion_correction[ - 'x_shifts_els'][:, b_id]), - 'y_std': np.nanstd(loaded_caiman.motion_correction[ - 'y_shifts_els'][:, b_id]), - 'z_std': (np.nanstd(loaded_caiman.motion_correction[ - 'z_shifts_els'][:, b_id]) - if is3D - else np.nan)}) - - self.NonRigidMotionCorrection.insert1(nonrigid_correction) - self.Block.insert(nonrigid_blocks) - - # -- summary images -- - field_keys = (scan.ScanInfo.Field & key).fetch('KEY', order_by='field_z') - summary_images = [ - {**key, **fkey, 'ref_image': ref_image, - 'average_image': ave_img, - 'correlation_image': corr_img, - 'max_proj_image': max_img} - for fkey, ref_image, ave_img, corr_img, max_img in zip( - field_keys, - loaded_caiman.motion_correction['reference_image'].transpose(2, 0, 1) - if is3D else loaded_caiman.motion_correction[ - 'reference_image'][...][np.newaxis, ...], - loaded_caiman.motion_correction['average_image'].transpose(2, 0, 1) - if is3D else loaded_caiman.motion_correction[ - 'average_image'][...][np.newaxis, ...], - loaded_caiman.motion_correction['correlation_image'].transpose(2, 0, 1) - if is3D else loaded_caiman.motion_correction[ - 'correlation_image'][...][np.newaxis, ...], - loaded_caiman.motion_correction['max_image'].transpose(2, 0, 1) - if is3D else loaded_caiman.motion_correction[ - 'max_image'][...][np.newaxis, ...])] - self.Summary.insert(summary_images) - - elif method == 'mcgill_miniscope_analysis': - loaded_miniscope_analysis = loaded_result - - # TODO: add motion correction and block data - - # -- summary images -- - mc_key = (scan.ScanInfo.Field * ProcessingTask & key).fetch1('KEY') - summary_images = {**mc_key, - 'average_image': loaded_miniscope_analysis.average_image, - 'correlation_image': loaded_miniscope_analysis.correlation_image} - - self.insert1({**key, 'motion_correct_channel': loaded_miniscope_analysis.alignment_channel}) - self.Summary.insert1(summary_images) - - else: - raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) - -# -------------- Segmentation -------------- - - -@schema -class Segmentation(dj.Computed): - definition = """ # Different mask segmentations. - -> Curation - --- - -> MotionCorrection - """ - - @property - def key_source(self): - return Curation & MotionCorrection - - class Mask(dj.Part): - definition = """ # A mask produced by segmentation. - -> master - mask : smallint - --- - -> scan.Channel.proj(segmentation_channel='channel') # channel used for segmentation - mask_npix : int # number of pixels in ROIs - mask_center_x=null : int # center x coordinate in pixel # TODO: determine why some masks don't have information, thus null required - mask_center_y=null : int # center y coordinate in pixel - mask_center_z=null : int # center z coordinate in pixel - mask_xpix=null : longblob # x coordinates in pixels - mask_ypix=null : longblob # y coordinates in pixels - mask_zpix=null : longblob # z coordinates in pixels - mask_weights : longblob # weights of the mask at the indices above - """ - - def make(self, key): - motion_correction_key = (MotionCorrection & key).fetch1('KEY') - - method, loaded_result = get_loader_result(key, Curation) - - if method == 'caiman': - loaded_caiman = loaded_result - - # infer "segmentation_channel" - from params if available, else from caiman loader - params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') - segmentation_channel = params.get('segmentation_channel', - loaded_caiman.segmentation_channel) - - masks, cells = [], [] - for mask in loaded_caiman.masks: - masks.append({**key, - 'segmentation_channel': segmentation_channel, - 'mask': mask['mask_id'], - 'mask_npix': mask['mask_npix'], - 'mask_center_x': mask['mask_center_x'], - 'mask_center_y': mask['mask_center_y'], - 'mask_center_z': mask['mask_center_z'], - 'mask_xpix': mask['mask_xpix'], - 'mask_ypix': mask['mask_ypix'], - 'mask_zpix': mask['mask_zpix'], - 'mask_weights': mask['mask_weights']}) - if loaded_caiman.cnmf.estimates.idx_components is not None: - if mask['mask_id'] in loaded_caiman.cnmf.estimates.idx_components: - cells.append({ - **key, - 'mask_classification_method': 'caiman_default_classifier', - 'mask': mask['mask_id'], 'mask_type': 'soma'}) - - self.insert1({**key, **motion_correction_key}) - self.Mask.insert(masks, ignore_extra_fields=True) - - if cells: - MaskClassification.insert1({ - **key, - 'mask_classification_method': 'caiman_default_classifier'}, - allow_direct_insert=True) - MaskClassification.MaskType.insert(cells, - ignore_extra_fields=True, - allow_direct_insert=True) - - elif method == 'mcgill_miniscope_analysis': - loaded_miniscope_analysis = loaded_result - - # infer "segmentation_channel" - from params if available, else from miniscope analysis loader - params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') - segmentation_channel = params.get('segmentation_channel', - loaded_miniscope_analysis.segmentation_channel) - - self.insert1(key) - self.Mask.insert([{**key, - 'segmentation_channel': segmentation_channel, - 'mask': mask['mask_id'], - 'mask_npix': mask['mask_npix'], - 'mask_center_x': mask['mask_center_x'], - 'mask_center_y': mask['mask_center_y'], - 'mask_xpix': mask['mask_xpix'], - 'mask_ypix': mask['mask_ypix'], - 'mask_weights': mask['mask_weights']} - for mask in loaded_miniscope_analysis.masks], - ignore_extra_fields=True) - - else: - raise NotImplementedError(f'Unknown/unimplemented method: {method}') - - -@schema -class MaskClassificationMethod(dj.Lookup): - definition = """ - mask_classification_method: varchar(48) - """ - - contents = zip(['caiman_default_classifier', - 'miniscope_analysis_default_classifier']) - - -@schema -class MaskClassification(dj.Computed): - definition = """ - -> Segmentation - -> MaskClassificationMethod - """ - - class MaskType(dj.Part): - definition = """ - -> master - -> Segmentation.Mask - --- - -> MaskType - confidence=null: float - """ - - def make(self, key): - pass - - -# -------------- Activity Trace -------------- - - -@schema -class Fluorescence(dj.Computed): - definition = """ # fluorescence traces before spike extraction or filtering - -> Segmentation - """ - - class Trace(dj.Part): - definition = """ - -> master - -> Segmentation.Mask - -> scan.Channel.proj(fluorescence_channel='channel') # the channel that this trace comes from - --- - fluorescence : longblob # fluorescence trace associated with this mask - neuropil_fluorescence=null : longblob # Neuropil fluorescence trace - """ - - def make(self, key): - method, loaded_result = get_loader_result(key, Curation) - - if method == 'caiman': - loaded_caiman = loaded_result - - # infer "segmentation_channel" - from params if available, else from caiman loader - params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') - segmentation_channel = params.get('segmentation_channel', - loaded_caiman.segmentation_channel) - - self.insert1(key) - self.Trace.insert([{**key, - 'mask': mask['mask_id'], - 'fluorescence_channel': segmentation_channel, - 'fluorescence': mask['inferred_trace']} - for mask in loaded_caiman.masks]) - - elif method == 'mcgill_miniscope_analysis': - loaded_miniscope_analysis = loaded_result - - # infer "segmentation_channel" - from params if available, else from miniscope analysis loader - params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') - segmentation_channel = params.get('segmentation_channel', - loaded_miniscope_analysis.segmentation_channel) - - self.insert1(key) - self.Trace.insert([{**key, - 'mask': mask['mask_id'], - 'fluorescence_channel': segmentation_channel, - 'fluorescence': mask['raw_trace']} - for mask in loaded_miniscope_analysis.masks]) - - else: - raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) - - -@schema -class ActivityExtractionMethod(dj.Lookup): - definition = """ - extraction_method: varchar(200) - """ - - contents = zip(['caiman_deconvolution', - 'caiman_dff', - 'mcgill_miniscope_analysis_deconvolution', - 'mcgill_miniscope_analysis_dff']) - - -@schema -class Activity(dj.Computed): - definition = """ # inferred neural activity from fluorescence trace - e.g. dff, spikes - -> Fluorescence - -> ActivityExtractionMethod - """ - - class Trace(dj.Part): - definition = """ # - -> master - -> Fluorescence.Trace - --- - activity_trace: longblob # - """ - - @property - def key_source(self): - caiman_key_source = (Fluorescence * ActivityExtractionMethod - * ProcessingParamSet.proj('processing_method') - & 'processing_method = "caiman"' - & 'extraction_method LIKE "caiman%"') - - miniscope_analysis_key_source = (Fluorescence * ActivityExtractionMethod - * ProcessingParamSet.proj('processing_method') - & 'processing_method = "mcgill_miniscope_analysis"' - & 'extraction_method LIKE "mcgill_miniscope_analysis%"') - - # TODO: fix #caiman_key_source.proj() + miniscope_analysis_key_source.proj() - return miniscope_analysis_key_source.proj() - - def make(self, key): - method, loaded_result = get_loader_result(key, Curation) - - if method == 'caiman': - loaded_caiman = loaded_result - - if key['extraction_method'] in ('caiman_deconvolution', 'caiman_dff'): - attr_mapper = {'caiman_deconvolution': 'spikes', 'caiman_dff': 'dff'} - - # infer "segmentation_channel" - from params if available, else from caiman loader - params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') - segmentation_channel = params.get('segmentation_channel', - loaded_caiman.segmentation_channel) - - self.insert1(key) - self.Trace.insert([{**key, - 'mask': mask['mask_id'], - 'fluorescence_channel': segmentation_channel, - 'activity_trace': mask[attr_mapper[key['extraction_method']]]} - for mask in loaded_caiman.masks]) - - elif method == 'mcgill_miniscope_analysis': - if key['extraction_method'] in ('mcgill_miniscope_analysis_deconvolution', 'mcgill_miniscope_analysis_dff'): - attr_mapper = {'mcgill_miniscope_analysis_deconvolution': 'spikes', 'mcgill_miniscope_analysis_dff': 'dff'} - - loaded_miniscope_analysis = loaded_result - - # infer "segmentation_channel" - from params if available, else from miniscope analysis loader - params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') - segmentation_channel = params.get('segmentation_channel', - loaded_miniscope_analysis.segmentation_channel) - - self.insert1(key) - self.Trace.insert([{**key, - 'mask': mask['mask_id'], - 'fluorescence_channel': segmentation_channel, - 'activity_trace': mask[attr_mapper[key['extraction_method']]]} - for mask in loaded_miniscope_analysis.masks]) - - else: - raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) - -# ---------------- HELPER FUNCTIONS ---------------- - - -_table_attribute_mapper = {'ProcessingTask': 'processing_output_dir', - 'Curation': 'curation_output_dir'} - - -def get_loader_result(key, table): - """ - Retrieve the loaded processed imaging results from the loader (e.g. caiman, etc.) - :param key: the `key` to one entry of ProcessingTask or Curation - :param table: the class defining the table to retrieve - the loaded results from (e.g. ProcessingTask, Curation) - :return: a loader object of the loaded results - (e.g. caiman.CaImAn, etc.) - """ - method, output_dir = (ProcessingParamSet * table & key).fetch1( - 'processing_method', _table_attribute_mapper[table.__name__]) - - root_dir = pathlib.Path(get_imaging_root_data_dir()) - output_dir = root_dir / output_dir - - if method == 'caiman': - from .readers import caiman_loader - loaded_output = caiman_loader.CaImAn(output_dir) - elif method == 'mcgill_miniscope_analysis': - from .readers import miniscope_analysis_loader - loaded_output = miniscope_analysis_loader.MiniscopeAnalysis(output_dir) - else: - raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) - - return method, loaded_output - - -def dict_to_uuid(key): - """ - Given a dictionary `key`, returns a hash string as UUID - """ - hashed = hashlib.md5() - for k, v in sorted(key.items()): - hashed.update(str(k).encode()) - hashed.update(str(v).encode()) - return uuid.UUID(hex=hashed.hexdigest()) diff --git a/element_miniscope/miniscope.py b/element_miniscope/miniscope.py new file mode 100644 index 0000000..c8a0264 --- /dev/null +++ b/element_miniscope/miniscope.py @@ -0,0 +1,857 @@ +import datajoint as dj +import numpy as np +import pathlib +from datetime import datetime +import importlib +import inspect +import cv2 +import json +import csv +import os +from element_interface.utils import dict_to_uuid, find_full_path, find_root_directory + +schema = dj.Schema() + +_linking_module = None + + +def activate(miniscope_schema_name, *, + create_schema=True, create_tables=True, linking_module=None): + """ + activate(miniscope_schema_name, *, create_schema=True, create_tables=True, linking_module=None) + :param miniscope_schema_name: schema name on the database server to activate the `miniscope` module + :param create_schema: when True (default), create schema in the database if it does not yet exist. + :param create_tables: when True (default), create tables in the database if they do not yet exist. + :param linking_module: a module name or a module containing the + required dependencies to activate the `miniscope` module: + Upstream tables: + + Session: parent table to Recording, + typically identifying a recording session + + Equipment: Reference table for Recording, + specifying the equipment used for the acquisition + + AnatomicalLocation: Reference table for RecordingLocation, specifying + the brain location where the recording is acquired + Functions: + + get_miniscope_root_data_dir() -> list + Retrieve the root data directory + Contains all subject/sessions data + :return: a string for full path to the root data directory + + get_session_directory(session_key: dict) -> str + Retrieve the session directory containing the recorded data for a + given session + :param session_key: a dictionary of one session `key` + :return: a string for full path to the session directory + + get_processed_root_data_dir() -> str: + Retrieves the root directory for all processed data + :return: a string for full path to the root directory for processed data + """ + + if isinstance(linking_module, str): + linking_module = importlib.import_module(linking_module) + assert inspect.ismodule(linking_module),\ + "The argument 'dependency' must be a module's name or a module" + + global _linking_module + _linking_module = linking_module + + schema.activate(miniscope_schema_name, create_schema=create_schema, + create_tables=create_tables, add_objects=_linking_module.__dict__) + + +# Functions required by the element-miniscope ----------------------------------------- + +def get_miniscope_root_data_dir() -> list: + """ + All data paths, directories in DataJoint Elements are recommended to be stored as + relative paths (posix format), with respect to some user-configured "root" + directory, which varies from machine to machine (e.g. different mounted drive + locations). + + get_miniscope_root_data_dir() -> list + This user-provided function retrieves the possible root data directories + containing the miniscope data for all subjects and sessions + (e.g. acquired Miniscope-DAQ-V4 raw files, + output files from processing routines, etc.). + + :return: a string for full path to the root data directory, + or list of strings for possible root data directories + """ + + root_directories = _linking_module.get_miniscope_root_data_dir() + if isinstance(root_directories, (str, pathlib.Path)): + root_directories = [root_directories] + + if hasattr(_linking_module, 'get_processed_root_data_dir'): + root_directories.append(_linking_module.get_processed_root_data_dir()) + + return root_directories + +def get_session_directory(session_key: dict) -> str: + """ + get_session_directory(session_key: dict) -> str + Retrieve the session directory containing the + recorded data for a given session + :param session_key: a dictionary of one session `key` + :return: a string for relative or full path to the session directory + """ + return _linking_module.get_session_directory(session_key) + +def get_processed_root_data_dir() -> str: + """ + get_processed_root_data_dir() -> str: + Retrieves the root directory for all processed data + :return: a string for full path to the root directory for processed data + """ + + if hasattr(_linking_module, 'get_processed_root_data_dir'): + return _linking_module.get_processed_root_data_dir() + else: + return get_miniscope_root_data_dir()[0] + + +# Experiment and analysis meta information ------------------------------------- + +@schema +class AcquisitionSoftware(dj.Lookup): + definition = """ + acquisition_software: varchar(24) + """ + contents = zip(['Miniscope-DAQ-V3', + 'Miniscope-DAQ-V4']) + + +@schema +class Channel(dj.Lookup): + definition = """ + channel : tinyint # 0-based indexing + """ + contents = zip(range(5)) + + +@schema +class Recording(dj.Manual): + definition = """ + -> Session + recording_id: int + --- + -> Equipment + -> AcquisitionSoftware + recording_directory: varchar(255) # relative to root data directory + recording_notes='' : varchar(4095) # free-notes + """ + + +@schema +class RecordingLocation(dj.Manual): + definition = """ + # Brain location where this miniscope recording is acquired + -> Recording + --- + -> AnatomicalLocation + """ + + +@schema +class RecordingInfo(dj.Imported): + definition = """ + # Store metadata about recording + -> Recording + --- + nchannels : tinyint # number of channels + nframes : int # number of recorded frames + px_height=null : smallint # height in pixels + px_width=null : smallint # width in pixels + um_height=null : float # height in microns + um_width=null : float # width in microns + fps : float # (Hz) frames per second + gain=null : float # recording gain + spatial_downsample=1 : tinyint # e.g. 1, 2, 4, 8. 1 for no downsampling + led_power : float # LED power used in the given recording + time_stamps : longblob # time stamps of each frame + recording_datetime=null : datetime # datetime of the recording + recording_duration=null : float # (seconds) duration of the recording + """ + + class File(dj.Part): + definition = """ + -> master + file_id : smallint unsigned + --- + file_path: varchar(255) # relative to root data directory + """ + + def make(self, key): + + # Search recording directory for miniscope raw files + acquisition_software, recording_directory = \ + (Recording & key).fetch1('acquisition_software' , + 'recording_directory') + + recording_path = find_full_path(get_miniscope_root_data_dir(), + recording_directory) + + recording_filepaths = [file_path.as_posix() for file_path + in recording_path.glob('*.avi')] + + if not recording_filepaths: + raise FileNotFoundError(f'No .avi files found in ' + f'{recording_directory}') + + if acquisition_software == 'Miniscope-DAQ-V3': + recording_timestamps = recording_path / 'timestamp.dat' + if not recording_timestamps.exists(): + raise FileNotFoundError(f'No timestamp file found in ' + f'{recording_directory}') + + nchannels = 1 # Assumes a single channel + + # Parse number of frames from timestamp.dat file + with open(recording_timestamps) as f: + next(f) + nframes = sum(1 for line in f if int(line[0]) == 0) + + # Parse image dimension and frame rate + video = cv2.VideoCapture(recording_filepaths[0]) + _, frame = video.read() + frame_size = np.shape(frame) + px_height = frame_size[0] + px_width = frame_size[1] + + fps = video.get(cv2.CAP_PROP_FPS) # TODO: Verify this method extracts correct value + + elif acquisition_software == 'Miniscope-DAQ-V4': + recording_metadata = list(recording_path.glob('metaData.json'))[0] + recording_timestamps = list(recording_path.glob('timeStamps.csv'))[0] + + if not recording_metadata.exists(): + raise FileNotFoundError(f'No .json file found in ' + f'{recording_directory}') + if not recording_timestamps.exists(): + raise FileNotFoundError(f'No timestamp (*.csv) file found in ' + f'{recording_directory}') + + with open(recording_metadata.as_posix()) as f: + metadata = json.loads(f.read()) + + with open(recording_timestamps, newline= '') as f: + time_stamps = list(csv.reader(f, delimiter=',')) + + nchannels = 1 # Assumes a single channel + nframes = len(time_stamps)-1 + px_height = metadata['ROI']['height'] + px_width = metadata['ROI']['width'] + fps = int(metadata['frameRate'].replace('FPS','')) + gain = metadata['gain'] + spatial_downsample = 1 # Assumes no spatial downsampling + led_power = metadata['led0'] + time_stamps = np.array([list(map(int, time_stamps[i])) + for i in range(1,len(time_stamps))]) + else: + raise NotImplementedError( + f'Loading routine not implemented for {acquisition_software}' + ' acquisition software') + + # Insert in RecordingInfo + self.insert1(dict(key, + nchannels=nchannels, + nframes=nframes, + px_height=px_height, + px_width=px_width, + fps=fps, + gain=gain, + spatial_downsample=spatial_downsample, + led_power=led_power, + time_stamps=time_stamps, + recording_duration=nframes / fps)) + + # Insert file(s) + recording_files = [pathlib.Path(f).relative_to( + find_root_directory( + get_miniscope_root_data_dir(), + f)).as_posix() + for f in recording_filepaths] + + self.File.insert([{**key, + 'file_id': i, + 'file_path': f} + for i, f in enumerate(recording_files)]) + + +# Trigger a processing routine ------------------------------------------------- + +@schema +class ProcessingMethod(dj.Lookup): + definition = """ + # Method, package, analysis software used for processing of miniscope data + # (e.g. CaImAn, etc.) + processing_method: char(16) + --- + processing_method_desc='': varchar(1000) + """ + + contents = [('caiman', 'caiman analysis suite')] + + +@schema +class ProcessingParamSet(dj.Lookup): + definition = """ + # Parameter set used for processing of miniscope data + paramset_id: smallint + --- + -> ProcessingMethod + paramset_desc: varchar(128) + param_set_hash: uuid + unique index (param_set_hash) + params: longblob # dictionary of all applicable parameters + """ + + @classmethod + def insert_new_params(cls, processing_method: str, paramset_id: int, + paramset_desc: str, params: dict, + processing_method_desc: str=''): + ProcessingMethod.insert1({'processing_method': processing_method}, + skip_duplicates=True) + param_dict = {'processing_method': processing_method, + 'paramset_id': paramset_id, + 'paramset_desc': paramset_desc, + 'params': params, + 'param_set_hash': dict_to_uuid(params)} + q_param = cls & {'param_set_hash': param_dict['param_set_hash']} + + if q_param: # If the specified param-set already exists + pname = q_param.fetch1('paramset_id') + if pname == paramset_id: # If the existed set has the same name: job done + return + else: # If not same name: human error, trying to add the same paramset with different name + raise dj.DataJointError( + 'The specified param-set already exists - name: {}'.format(pname)) + else: + cls.insert1(param_dict) + +@schema +class ProcessingTask(dj.Manual): + definition = """ + # Manual table marking a processing task to be triggered or manually processed + -> RecordingInfo + -> ProcessingParamSet + --- + processing_output_dir : varchar(255) # relative to the root data directory + task_mode='load' : enum('load', 'trigger') # 'load': load existing results + # 'trigger': trigger procedure + """ + +@schema +class Processing(dj.Computed): + definition = """ + -> ProcessingTask + --- + processing_time : datetime # time of generation of this set of processed, segmented results + package_version='' : varchar(16) + """ + + def make(self, key): + task_mode = (ProcessingTask & key).fetch1('task_mode') + + output_dir = (ProcessingTask & key).fetch1('processing_output_dir') + output_dir = find_full_path(get_miniscope_root_data_dir(), output_dir) + + if task_mode == 'load': + method, loaded_result = get_loader_result(key, ProcessingTask) + if method == 'caiman': + loaded_caiman = loaded_result + key = {**key, 'processing_time': loaded_caiman.creation_time} + else: + raise NotImplementedError(f'Loading of {method} data is not yet' + f'supported') + elif task_mode == 'trigger': + method = (ProcessingTask * ProcessingParamSet * ProcessingMethod * \ + Recording & key).fetch1('processing_method') + + if method == 'caiman': + import caiman + from element_interface.run_caiman import run_caiman + + avi_files = (Recording * RecordingInfo * RecordingInfo.File \ + & key).fetch('file_path') + avi_files = [find_full_path(get_miniscope_root_data_dir(), + avi_file).as_posix() for avi_file in avi_files] + + params = (ProcessingTask * ProcessingParamSet & key).fetch1('params') + sampling_rate = (ProcessingTask * Recording * RecordingInfo \ + & key).fetch1('fps') + + input_hash = dict_to_uuid(dict(**key, **params)) + input_hash_fp = output_dir / f'.{input_hash }.json' + + if not input_hash_fp.exist(): + start_time = datetime.utcnow() + run_caiman(file_paths=avi_files, + parameters=params, + sampling_rate=sampling_rate, + output_dir=output_dir.as_posix(), + is3D=False) + completion_time = datetime.utcnow() + with open(input_hash_fp, 'w') as f: + json.dump({'start_time': start_time , + 'completion_time': completion_time , + 'duration': (completion_time - start_time).total_seconds()}, f, default=str) + + _, imaging_dataset = get_loader_result(key, ProcessingTask) + caiman_dataset = imaging_dataset + key['processing_time'] = caiman_dataset.creation_time + key['package_version'] = caiman.__version__ + else: + raise NotImplementedError(f'Automatic triggering of {method} analysis' + f' is not yet supported') + else: + raise ValueError(f'Unknown task mode: {task_mode}') + + self.insert1(key) + +@schema +class Curation(dj.Manual): + definition = """ + # Different rounds of curation performed on the processing results of the data + # (no-curation can also be included here) + -> Processing + curation_id: int + --- + curation_time: datetime # time of generation of these curated results + curation_output_dir: varchar(255) # output directory of the curated results, + # relative to root data directory + manual_curation: bool # has manual curation been performed? + curation_note='': varchar(2000) + """ + + def create1_from_processing_task(self, key, is_curated=False, curation_note=''): + """ + A convenient function to create a new corresponding "Curation" for a particular "ProcessingTask" + """ + if key not in Processing(): + raise ValueError(f'No corresponding entry in Processing available for: ' + f'{key}; run `Processing.populate(key)`') + + output_dir = (ProcessingTask & key).fetch1('processing_output_dir') + method, imaging_dataset = get_loader_result(key, ProcessingTask) + + if method == 'caiman': + caiman_dataset = imaging_dataset + curation_time = caiman_dataset.creation_time + else: + raise NotImplementedError('Unknown method: {}'.format(method)) + + # Synthesize curation_id + curation_id = dj.U().aggr(self & key, n='ifnull(max(curation_id)+1,1)').fetch1('n') + self.insert1({**key, + 'curation_id': curation_id, + 'curation_time': curation_time, + 'curation_output_dir': output_dir, + 'manual_curation': is_curated, + 'curation_note': curation_note}) + + +# Motion Correction -------------------------------------------------------------------- + +@schema +class MotionCorrection(dj.Imported): + definition = """ + -> Curation + --- + -> Channel.proj(motion_correct_channel='channel') # channel used for + # motion correction + """ + + class RigidMotionCorrection(dj.Part): + definition = """ + -> master + --- + outlier_frames=null : longblob # mask with true for frames with outlier shifts + # (already corrected) + y_shifts : longblob # (pixels) y motion correction shifts + x_shifts : longblob # (pixels) x motion correction shifts + y_std : float # (pixels) standard deviation of + # y shifts across all frames + x_std : float # (pixels) standard deviation of + # x shifts across all frames + """ + + class NonRigidMotionCorrection(dj.Part): + """ + Piece-wise rigid motion correction + """ + definition = """ + -> master + --- + outlier_frames=null : longblob # mask with true for frames with + # outlier shifts (already corrected) + block_height : int # (pixels) + block_width : int # (pixels) + block_count_y : int # number of blocks tiled in the + # y direction + block_count_x : int # number of blocks tiled in the + # x direction + """ + + class Block(dj.Part): + definition = """ # FOV-tiled blocks used for non-rigid motion correction + -> master.NonRigidMotionCorrection + block_id : int + --- + block_y : longblob # (y_start, y_end) in pixel of this block + block_x : longblob # (x_start, x_end) in pixel of this block + y_shifts : longblob # (pixels) y motion correction shifts for + # every frame + x_shifts : longblob # (pixels) x motion correction shifts for + # every frame + y_std : float # (pixels) standard deviation of y shifts + # across all frames + x_std : float # (pixels) standard deviation of x shifts + # across all frames + """ + + class Summary(dj.Part): + definition = """ # summary images for each field and channel after corrections + -> master + --- + ref_image=null : longblob # image used as alignment template + average_image : longblob # mean of registered frames + correlation_image=null : longblob # correlation map + # (computed during cell detection) + max_proj_image=null : longblob # max of registered frames + """ + + def make(self, key): + method, loaded_result = get_loader_result(key, ProcessingTask) + + if method == 'caiman': + loaded_caiman = loaded_result + + self.insert1({**key, + 'motion_correct_channel': loaded_caiman.alignment_channel}) + + # -- rigid motion correction -- + if not loaded_caiman.params.motion['pw_rigid']: + rigid_correction = { + **key, + 'x_shifts': loaded_caiman.motion_correction['shifts_rig'][:, 0], + 'y_shifts': loaded_caiman.motion_correction['shifts_rig'][:, 1], + 'x_std': np.nanstd(loaded_caiman.motion_correction[ + 'shifts_rig'][:, 0]), + 'y_std': np.nanstd(loaded_caiman.motion_correction[ + 'shifts_rig'][:, 1]), + 'outlier_frames': None} + + self.RigidMotionCorrection.insert1(rigid_correction) + + # -- non-rigid motion correction -- + else: + nonrigid_correction = { + **key, + 'block_height': (loaded_caiman.params.motion['strides'][0] + + loaded_caiman.params.motion['overlaps'][0]), + 'block_width': (loaded_caiman.params.motion['strides'][1] + + loaded_caiman.params.motion['overlaps'][1]), + 'block_count_x': len( + set(loaded_caiman.motion_correction['coord_shifts_els'][:, 0])), + 'block_count_y': len( + set(loaded_caiman.motion_correction['coord_shifts_els'][:, 2])), + 'outlier_frames': None} + + nonrigid_blocks = [] + for b_id in range(len(loaded_caiman.motion_correction['x_shifts_els'][0, :])): + nonrigid_blocks.append( + {**key, 'block_id': b_id, + 'block_x': np.arange(*loaded_caiman.motion_correction[ + 'coord_shifts_els'][b_id, 0:2]), + 'block_y': np.arange(*loaded_caiman.motion_correction[ + 'coord_shifts_els'][b_id, 2:4]), + 'x_shifts': loaded_caiman.motion_correction[ + 'x_shifts_els'][:, b_id], + 'y_shifts': loaded_caiman.motion_correction[ + 'y_shifts_els'][:, b_id], + 'x_std': np.nanstd(loaded_caiman.motion_correction[ + 'x_shifts_els'][:, b_id]), + 'y_std': np.nanstd(loaded_caiman.motion_correction[ + 'y_shifts_els'][:, b_id])}) + + self.NonRigidMotionCorrection.insert1(nonrigid_correction) + self.Block.insert(nonrigid_blocks) + + # -- summary images -- + summary_images = {**key, + 'ref_image': loaded_caiman.motion_correction[ + 'reference_image'][...][np.newaxis, ...], + 'average_image': loaded_caiman.motion_correction[ + 'average_image'][...][np.newaxis, ...], + 'correlation_image': loaded_caiman.motion_correction[ + 'correlation_image'][...][np.newaxis, ...], + 'max_proj_image': loaded_caiman.motion_correction[ + 'max_image'][...][np.newaxis, ...]} + + self.Summary.insert1(summary_images) + + else: + raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) + +# Segmentation ------------------------------------------------------------------------- + +@schema +class Segmentation(dj.Computed): + definition = """ # Different mask segmentations. + -> Curation + """ + + class Mask(dj.Part): + definition = """ # A mask produced by segmentation. + -> master + mask_id : smallint + --- + -> Channel.proj(segmentation_channel='channel') # channel used for segmentation + mask_npix : int # number of pixels in this mask + mask_center_x=null : int # (pixels) center x coordinate + # TODO: determine why some masks don't have information, thus null required + mask_center_y=null : int # (pixels) center y coordinate + mask_xpix=null : longblob # (pixels) x coordinates + mask_ypix=null : longblob # (pixels) y coordinates + mask_weights : longblob # weights of the mask at the indices above + """ + + def make(self, key): + method, loaded_result = get_loader_result(key, Curation) + + if method == 'caiman': + loaded_caiman = loaded_result + + # infer `segmentation_channel` from `params` if available, + # else from caiman loader + params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') + segmentation_channel = params.get('segmentation_channel', + loaded_caiman.segmentation_channel) + + masks, cells = [], [] + for mask in loaded_caiman.masks: + masks.append({**key, + 'segmentation_channel': segmentation_channel, + 'mask_id': mask['mask_id'], + 'mask_npix': mask['mask_npix'], + 'mask_center_x': mask['mask_center_x'], + 'mask_center_y': mask['mask_center_y'], + 'mask_xpix': mask['mask_xpix'], + 'mask_ypix': mask['mask_ypix'], + 'mask_weights': mask['mask_weights']}) + + if loaded_caiman.cnmf.estimates.idx_components is not None: + if mask['mask_id'] in loaded_caiman.cnmf.estimates.idx_components: + cells.append({ + **key, + 'mask_classification_method': 'caiman_default_classifier', + 'mask_id': mask['mask_id'], + 'mask_type': 'soma'}) + + self.insert1(key) + self.Mask.insert(masks, ignore_extra_fields=True) + + if cells: + MaskClassification.insert1({ + **key, + 'mask_classification_method': 'caiman_default_classifier'}, + allow_direct_insert=True) + MaskClassification.MaskType.insert(cells, + ignore_extra_fields=True, + allow_direct_insert=True) + + else: + raise NotImplementedError(f'Unknown/unimplemented method: {method}') + + +@schema +class MaskType(dj.Lookup): + definition = """ # Possible classifications for a segmented mask + mask_type : varchar(16) + """ + + contents = zip(['soma', 'axon', 'dendrite', 'neuropil', 'artefact', 'unknown']) + + +@schema +class MaskClassificationMethod(dj.Lookup): + definition = """ + mask_classification_method: varchar(48) + """ + + contents = zip(['caiman_default_classifier']) + + +@schema +class MaskClassification(dj.Computed): + definition = """ + -> Segmentation + -> MaskClassificationMethod + """ + + class MaskType(dj.Part): + definition = """ + -> master + -> Segmentation.Mask + --- + -> MaskType + confidence=null: float + """ + + def make(self, key): + pass + + +# Fluorescence & Activity Traces ------------------------------------------------------- + +@schema +class Fluorescence(dj.Computed): + definition = """ # fluorescence traces before spike extraction or filtering + -> Segmentation + """ + + class Trace(dj.Part): + definition = """ + -> master + -> Segmentation.Mask + -> Channel.proj(fluorescence_channel='channel') # channel used for this trace + --- + fluorescence : longblob # fluorescence trace associated + # with this mask + neuropil_fluorescence=null : longblob # Neuropil fluorescence trace + """ + + def make(self, key): + method, loaded_result = get_loader_result(key, Curation) + + if method == 'caiman': + loaded_caiman = loaded_result + + # infer `segmentation_channel` from `params` if available, + # else from caiman loader + params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') + segmentation_channel = params.get('segmentation_channel', + loaded_caiman.segmentation_channel) + + self.insert1(key) + self.Trace.insert([{**key, + 'mask_id': mask['mask_id'], + 'fluorescence_channel': segmentation_channel, + 'fluorescence': mask['inferred_trace']} + for mask in loaded_caiman.masks]) + + + else: + raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) + + +@schema +class ActivityExtractionMethod(dj.Lookup): + definition = """ + extraction_method: varchar(200) + """ + + contents = zip(['caiman_deconvolution', + 'caiman_dff']) + + +@schema +class Activity(dj.Computed): + definition = """ + # inferred neural activity from fluorescence trace - e.g. dff, spikes + -> Fluorescence + -> ActivityExtractionMethod + """ + + class Trace(dj.Part): + definition = """ + -> master + -> Fluorescence.Trace + --- + activity_trace: longblob + """ + + @property + def key_source(self): + caiman_key_source = (Fluorescence * ActivityExtractionMethod + * ProcessingParamSet.proj('processing_method') + & 'processing_method = "caiman"' + & 'extraction_method LIKE "caiman%"') + + return caiman_key_source.proj() + + def make(self, key): + method, loaded_result = get_loader_result(key, Curation) + + if method == 'caiman': + loaded_caiman = loaded_result + + if key['extraction_method'] in ('caiman_deconvolution', 'caiman_dff'): + attr_mapper = {'caiman_deconvolution': 'spikes', 'caiman_dff': 'dff'} + + # infer `segmentation_channel` from `params` if available, + # else from caiman loader + params = (ProcessingParamSet * ProcessingTask & key).fetch1('params') + segmentation_channel = params.get('segmentation_channel', + loaded_caiman.segmentation_channel) + + self.insert1(key) + self.Trace.insert([{**key, + 'mask_id': mask['mask_id'], + 'fluorescence_channel': segmentation_channel, + 'activity_trace': mask[attr_mapper[key['extraction_method']]]} + for mask in loaded_caiman.masks]) + + + else: + raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) + + +# Helper Functions --------------------------------------------------------------------- + +_table_attribute_mapper = {'ProcessingTask': 'processing_output_dir', + 'Curation': 'curation_output_dir'} + + +def get_loader_result(key, table): + """ + Retrieve the loaded processed imaging results from the loader (e.g. caiman, etc.) + :param key: the `key` to one entry of ProcessingTask or Curation + :param table: the class defining the table to retrieve + the loaded results from (e.g. ProcessingTask, Curation) + :return: a loader object of the loaded results + (e.g. caiman.CaImAn, etc.) + """ + method, output_dir = (ProcessingParamSet * table & key).fetch1( + 'processing_method', _table_attribute_mapper[table.__name__]) + + output_dir = find_full_path(get_miniscope_root_data_dir(), output_dir) + + if method == 'caiman': + from element_interface import caiman_loader + loaded_output = caiman_loader.CaImAn(output_dir) + else: + raise NotImplementedError('Unknown/unimplemented method: {}'.format(method)) + + return method, loaded_output + + +def populate_all(display_progress=True, reserve_jobs=False, suppress_errors=False): + + populate_settings = {'display_progress': display_progress, + 'reserve_jobs': reserve_jobs, + 'suppress_errors': suppress_errors} + + RecordingInfo.populate(**populate_settings) + + Processing.populate(**populate_settings) + + MotionCorrection.populate(**populate_settings) + + Segmentation.populate(**populate_settings) + + MaskClassification.populate(**populate_settings) + + Fluorescence.populate(**populate_settings) + + Activity.populate(**populate_settings) \ No newline at end of file diff --git a/element_miniscope/readers/__init__.py b/element_miniscope/readers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/element_miniscope/readers/caiman_loader.py b/element_miniscope/readers/caiman_loader.py deleted file mode 100644 index cde3e6d..0000000 --- a/element_miniscope/readers/caiman_loader.py +++ /dev/null @@ -1,187 +0,0 @@ -import h5py -import caiman as cm -import scipy -import numpy as np -from datetime import datetime -import os -import pathlib -from tqdm import tqdm - - -_required_hdf5_fields = ['/motion_correction/reference_image', - '/motion_correction/correlation_image', - '/motion_correction/average_image', - '/motion_correction/max_image', - '/estimates/A'] - - -class CaImAn: - """ - Parse the CaImAn output file - Expecting the following objects: - - 'dims': - - 'dview': - - 'estimates': Segmentations and traces - - 'mmap_file': - - 'params': Input parameters - - 'remove_very_bad_comps': - - 'skip_refinement': - - 'motion_correction': Motion correction shifts and summary images - CaImAn results doc: https://caiman.readthedocs.io/en/master/Getting_Started.html#result-variables-for-2p-batch-analysis - """ - - def __init__(self, caiman_dir): - # ---- Search and verify CaImAn output file exists ---- - caiman_dir = pathlib.Path(caiman_dir) - if not caiman_dir.exists(): - raise FileNotFoundError('CaImAn directory not found: {}'.format(caiman_dir)) - - for fp in caiman_dir.glob('*.hdf5'): - with h5py.File(fp, 'r') as h5f: - if all(s in h5f for s in _required_hdf5_fields): - self.caiman_fp = fp - - # ---- Initialize CaImAn's results ---- - self.cnmf = cm.source_extraction.cnmf.cnmf.load_CNMF(self.caiman_fp) - self.params = self.cnmf.params - - self.h5f = h5py.File(self.caiman_fp, 'r') - self.motion_correction = self.h5f['motion_correction'] - self._masks = None - - # ---- Metainfo ---- - self.creation_time = datetime.fromtimestamp(os.stat(self.caiman_fp).st_ctime) - self.curation_time = datetime.fromtimestamp(os.stat(self.caiman_fp).st_ctime) - - @property - def masks(self): - if self._masks is None: - self._masks = self.extract_masks() - return self._masks - - @property - def alignment_channel(self): - return 0 # hard-code to channel index 0 - - @property - def segmentation_channel(self): - return 0 # hard-code to channel index 0 - - def extract_masks(self): - if self.params.motion['is3D']: - raise NotImplemented('CaImAn mask extraction for volumetric data not yet implemented') - - comp_contours = cm.utils.visualization.get_contours( - self.cnmf.estimates.A, self.cnmf.dims) - - masks = [] - for comp_idx, comp_contour in enumerate(comp_contours): - ind, _, weights = scipy.sparse.find(self.cnmf.estimates.A[:, comp_idx]) - if self.cnmf.params.motion['is3D']: - xpix, ypix, zpix = np.unravel_index(ind, self.cnmf.dims, order='F') - center_x, center_y, center_z = comp_contour['CoM'].astype(int) - else: - xpix, ypix = np.unravel_index(ind, self.cnmf.dims, order='F') - center_x, center_y = comp_contour['CoM'].astype(int) - center_z = 0 - zpix = np.full(len(weights), center_z) - - masks.append({'mask_id': comp_contour['neuron_id'], - 'mask_npix': len(weights), 'mask_weights': weights, - 'mask_center_x': center_x, - 'mask_center_y': center_y, - 'mask_center_z': center_z, - 'mask_xpix': xpix, 'mask_ypix': ypix, 'mask_zpix': zpix, - 'inferred_trace': self.cnmf.estimates.C[comp_idx, :], - 'dff': self.cnmf.estimates.F_dff[comp_idx, :], - 'spikes': self.cnmf.estimates.S[comp_idx, :]}) - return masks - - -def save_mc(mc, caiman_fp, is3D): - """ - DataJoint Imaging Element - CaImAn Integration - Run these commands after the CaImAn analysis has completed. - This will save the relevant motion correction data into the '*.hdf5' file. - Please do not clear variables from memory prior to running these commands. - The motion correction (mc) object will be read from memory. - - 'mc' : CaImAn motion correction object - 'caiman_fp' : CaImAn output (*.hdf5) file path - - 'shifts_rig' : Rigid transformation x and y shifts per frame - 'x_shifts_els' : Non rigid transformation x shifts per frame per block - 'y_shifts_els' : Non rigid transformation y shifts per frame per block - """ - - # Load motion corrected mmap image - mc_image = cm.load(mc.mmap_file, is3D=is3D) - - # Compute motion corrected summary images - average_image = np.mean(mc_image, axis=0) - max_image = np.max(mc_image, axis=0) - - # Compute motion corrected correlation image - correlation_image = cm.local_correlations(mc_image.transpose((1, 2, 3, 0) - if is3D else (1, 2, 0))) - correlation_image[np.isnan(correlation_image)] = 0 - - # Compute mc.coord_shifts_els - grid = [] - if is3D: - for _, _, _, x, y, z, _ in cm.motion_correction.sliding_window_3d( - mc_image[0, :, :, :], mc.overlaps, mc.strides): - grid.append([x, x + mc.overlaps[0] + mc.strides[0], - y, y + mc.overlaps[1] + mc.strides[1], - z, z + mc.overlaps[2] + mc.strides[2]]) - else: - for _, _, x, y, _ in cm.motion_correction.sliding_window( - mc_image[0, :, :], mc.overlaps, mc.strides): - grid.append([x, x + mc.overlaps[0] + mc.strides[0], - y, y + mc.overlaps[1] + mc.strides[1]]) - - # Open hdf5 file and create 'motion_correction' group - h5f = h5py.File(caiman_fp, 'r+') - h5g = h5f.require_group("motion_correction") - - # Write motion correction shifts and motion corrected summary images to hdf5 file - if mc.pw_rigid: - h5g.require_dataset("x_shifts_els", shape=np.shape(mc.x_shifts_els), - data=mc.x_shifts_els, - dtype=mc.x_shifts_els[0][0].dtype) - h5g.require_dataset("y_shifts_els", shape=np.shape(mc.y_shifts_els), - data=mc.y_shifts_els, - dtype=mc.y_shifts_els[0][0].dtype) - if is3D: - h5g.require_dataset("z_shifts_els", shape=np.shape(mc.z_shifts_els), - data=mc.z_shifts_els, - dtype=mc.z_shifts_els[0][0].dtype) - - h5g.require_dataset("coord_shifts_els", shape=np.shape(grid), - data=grid, dtype=type(grid[0][0])) - - # For CaImAn, reference image is still a 2D array even for the case of 3D - # Assume that the same ref image is used for all the planes - reference_image = np.tile(mc.total_template_els, (1, 1, correlation_image.shape[-1]))\ - if is3D else mc.total_template_els - else: - h5g.require_dataset("shifts_rig", shape=np.shape(mc.shifts_rig), - data=mc.shifts_rig, dtype=mc.shifts_rig[0].dtype) - h5g.require_dataset("coord_shifts_rig", shape=np.shape(grid), - data=grid, dtype=type(grid[0][0])) - reference_image = np.tile(mc.total_template_rig, (1, 1, correlation_image.shape[-1]))\ - if is3D else mc.total_template_rig - - h5g.require_dataset("reference_image", shape=np.shape(reference_image), - data=reference_image, - dtype=reference_image.dtype) - h5g.require_dataset("correlation_image", shape=np.shape(correlation_image), - data=correlation_image, - dtype=correlation_image.dtype) - h5g.require_dataset("average_image", shape=np.shape(average_image), - data=average_image, dtype=average_image.dtype) - h5g.require_dataset("max_image", shape=np.shape(max_image), - data=max_image, dtype=max_image.dtype) - - # Close hdf5 file - h5f.close() diff --git a/element_miniscope/readers/miniscope_analysis_loader.py b/element_miniscope/readers/miniscope_analysis_loader.py deleted file mode 100644 index c35d189..0000000 --- a/element_miniscope/readers/miniscope_analysis_loader.py +++ /dev/null @@ -1,88 +0,0 @@ -import numpy as np -import h5py -from datetime import datetime -import os -import pathlib -import scipy.ndimage - -_required_mat_ms_fields = ['Options', - 'meanFrame', - 'CorrProj', - 'PeakToNoiseProj', - 'RawTraces', - 'FiltTraces', - 'DeconvolvedTraces'] - - -class MiniscopeAnalysis: - """ - Parse the Miniscope Analysis output files - Miniscope Analysis repository: https://github.com/etterguillaume/MiniscopeAnalysis - Expecting the following objects: - - 'SFP.mat': Spatial footprints of the cells found while performing CNMFE extraction. - - 'ms.mat': - - 'ms[Options]': Parameters used to perform CNMFE. - - 'ms[meanFrame]': - - 'ms[CorrProj]': Correlation projection from the CNMFE. Displays which pixels are correlated together and suggests the location of your cells. - - 'ms[PeaktoNoiseProj]': Peak-to-noise ratio of the correlation projection. Gives you an idea of most prominent cells in your recording. - - 'ms[RawTraces]': - - 'ms[FiltTraces]': - - 'ms[DeconvolvedTraces]': - """ - - def __init__(self, miniscope_analysis_dir): - # ---- Search and verify Miniscope Analysis output file exists ---- - miniscope_analysis_dir = pathlib.Path(miniscope_analysis_dir) - if not miniscope_analysis_dir.exists(): - raise FileNotFoundError(f'Miniscope Analysis directory not found: {miniscope_analysis_dir}') - - self.miniscope_fp_ms = f'{miniscope_analysis_dir}/ms.mat' - self.miniscope_fp_sfp = f'{miniscope_analysis_dir}/SFP.mat' - self.mat_ms = h5py.File(self.miniscope_fp_ms, 'r') - self.mat_sfp = h5py.File(self.miniscope_fp_sfp, 'r') - - if not all(s in self.mat_ms['ms'] for s in _required_mat_ms_fields): - raise ValueError(f'Miniscope Analysis file {self.miniscope_fp_ms} does not have all required fields.') - - # ---- Initialize Miniscope Analysis results ---- - self.params = self.mat_ms['ms']['Options'] - self.average_image = self.mat_ms['ms']['meanFrame'][...] - self.correlation_image = self.mat_ms['ms']['CorrProj'][...] - self._masks = None - - # ---- Metainfo ---- - self.creation_time = datetime.fromtimestamp(os.stat(self.miniscope_fp_ms).st_ctime) - self.curation_time = datetime.fromtimestamp(os.stat(self.miniscope_fp_ms).st_ctime) - - @property - def masks(self): - if self._masks is None: - self._masks = self.extract_masks() - return self._masks - - @property - def alignment_channel(self): - return 0 # hard-code to channel index 0 - - @property - def segmentation_channel(self): - return 0 # hard-code to channel index 0 - - def extract_masks(self): - masks = [] - for i in range(int(self.mat_ms['ms']['numNeurons'][0,0])): - center_y, center_x = scipy.ndimage.measurements.center_of_mass(self.mat_sfp['SFP'][i,:,:]) - xpix, ypix, weights = scipy.sparse.find(self.mat_sfp['SFP'][i,:,:]) - - masks.append({'mask_id': i, - 'mask_npix': len(weights), - 'mask_center_x': center_x, - 'mask_center_y': center_y, - 'mask_xpix': xpix, - 'mask_ypix': ypix, - 'mask_weights': weights, - 'raw_trace': self.mat_ms['ms']['RawTraces'][i,:], - 'dff': self.mat_ms['ms']['FiltTraces'][i,:], - 'spikes': self.mat_ms['ms']['DeconvolvedTraces'][i,:]}) - return masks - diff --git a/element_miniscope/scan.py b/element_miniscope/scan.py deleted file mode 100644 index c37cf51..0000000 --- a/element_miniscope/scan.py +++ /dev/null @@ -1,185 +0,0 @@ -import datajoint as dj -import pathlib -import importlib -import inspect -import numpy as np - -schema = dj.schema() - -_linking_module = None - - -def activate(scan_schema_name, *, create_schema=True, create_tables=True, linking_module=None): - """ - activate(scan_schema_name, *, create_schema=True, create_tables=True, linking_module=None) - :param scan_schema_name: schema name on the database server to activate the `scan` module - :param create_schema: when True (default), create schema in the database if it does not yet exist. - :param create_tables: when True (default), create tables in the database if they do not yet exist. - :param linking_module: a module name or a module containing the - required dependencies to activate the `scan` module: - Upstream tables: - + Session: parent table to Scan, typically identifying a recording session - + Equipment: Reference table for Scan, specifying the equipment used for the acquisition of this scan - + Location: Reference table for ScanLocation, specifying the brain location where this scan is acquired - Functions: - + get_imaging_root_data_dir() -> str - Retrieve the full path for the root data directory (e.g. the mounted drive) - :return: a string with full path to the root data directory - + get_miniscope_daq_v3_file(scan_key: dict) -> str - Retrieve the Miniscope DAQ V3 files associated with a given Scan - :param scan_key: key of a Scan - :return: Miniscope DAQ V3 files full file-path - """ - - if isinstance(linking_module, str): - linking_module = importlib.import_module(linking_module) - assert inspect.ismodule(linking_module),\ - "The argument 'dependency' must be a module's name or a module" - - global _linking_module - _linking_module = linking_module - - schema.activate(scan_schema_name, create_schema=create_schema, - create_tables=create_tables, add_objects=_linking_module.__dict__) - - -# ---------------- Functions required by the element-miniscope ---------------- - - -def get_imaging_root_data_dir() -> str: - """ - Retrieve the full path for the root data directory (e.g. the mounted drive) - :return: a string with full path to the root data directory - """ - return _linking_module.get_imaging_root_data_dir() - -def get_miniscope_daq_v3_files(scan_key: dict) -> str: - """ - Retrieve the Miniscope DAQ V3 files associated with a given Scan - :param scan_key: key of a Scan - :return: Miniscope DAQ V3 files full file-path - """ - return _linking_module.get_miniscope_daq_v3_files(scan_key) - - -# ----------------------------- Table declarations ----------------------------- - - -@schema -class AcquisitionSoftware(dj.Lookup): - definition = """ # Name of acquisition software - acq_software: varchar(24) - """ - contents = zip(['Miniscope-DAQ-V3']) - - -@schema -class Channel(dj.Lookup): - definition = """ # Recording channel - channel : tinyint # 0-based indexing - """ - contents = zip(range(5)) - - -# ------------------------------------ Scan ------------------------------------ - - -@schema -class Scan(dj.Manual): - definition = """ - -> Session - scan_id: int - --- - -> Equipment - -> AcquisitionSoftware - scan_notes='' : varchar(4095) # free-notes - """ - - -@schema -class ScanLocation(dj.Manual): - definition = """ - -> Scan - --- - -> Location - """ - - -@schema -class ScanInfo(dj.Imported): - definition = """ # general data about the reso/meso scans - -> Scan - --- - nfields : tinyint # number of fields - nchannels : tinyint # number of channels - ndepths : int # Number of scanning depths (planes) - nframes : int # number of recorded frames - nrois : tinyint # number of regions of interest - x=null : float # (um) 0 point in the motor coordinate system - y=null : float # (um) 0 point in the motor coordinate system - z=null : float # (um) 0 point in the motor coordinate system - fps : float # (Hz) frames per second - volumetric scan rate - """ - - class Field(dj.Part): - definition = """ # field-specific scan information - -> master - field_idx : int - --- - px_height : smallint # height in pixels - px_width : smallint # width in pixels - um_height=null : float # height in microns - um_width=null : float # width in microns - field_x=null : float # (um) center of field in the motor coordinate system - field_y=null : float # (um) center of field in the motor coordinate system - field_z=null : float # (um) relative depth of field - delay_image=null : longblob # (ms) delay between the start of the scan and pixels in this field - """ - - class ScanFile(dj.Part): - definition = """ - -> master - file_path: varchar(255) # filepath relative to root data directory - """ - - def make(self, key): - """ Read and store some scan meta information.""" - acq_software = (Scan & key).fetch1('acq_software') - - if acq_software == 'Miniscope-DAQ-V3': - # Parse image dimension and frame rate - import cv2 - scan_filepaths = get_miniscope_daq_v3_files(key) - video = cv2.VideoCapture(scan_filepaths[0]) - fps = video.get(cv2.CAP_PROP_FPS) # TODO: Verify this method extracts correct value - _, frame = video.read() - frame_size = np.shape(frame) - - # Parse number of frames from timestamp.dat file - with open(scan_filepaths[-1]) as f: - next(f) - nframes = sum(1 for line in f if int(line[0]) == 0) - - # Insert in ScanInfo - self.insert1(dict(key, - nfields=1, - nchannels=1, - nframes=nframes, - ndepths=1, - fps=fps, - nrois=0)) - - # Insert Field(s) - self.Field.insert1(dict(key, - field_idx=0, - px_height=frame_size[0], - px_width=frame_size[1])) - - else: - raise NotImplementedError( - f'Loading routine not implemented for {acq_software} acquisition software') - - # Insert file(s) - root = pathlib.Path(get_imaging_root_data_dir()) - scan_files = [pathlib.Path(f).relative_to(root).as_posix() for f in scan_filepaths] - self.ScanFile.insert([{**key, 'file_path': f} for f in scan_files]) diff --git a/element_miniscope/version.py b/element_miniscope/version.py index cc61870..fb30593 100644 --- a/element_miniscope/version.py +++ b/element_miniscope/version.py @@ -1,2 +1,2 @@ """Package metadata""" -__version__ = '0.1.1' \ No newline at end of file +__version__ = '0.1.0' \ No newline at end of file diff --git a/images/attached_miniscope_element.svg b/images/attached_miniscope_element.svg index aa98758..3385e7c 100644 --- a/images/attached_miniscope_element.svg +++ b/images/attached_miniscope_element.svg @@ -1,3 +1,1622 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="828px" height="881px" viewBox="-0.5 -0.5 828 881" content="<mxfile host="app.diagrams.net" modified="2021-03-19T15:14:30.074Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36" etag="SSflg09Z1BEoJnyAogEr" version="14.4.8" type="google" pages="2"><diagram id="2QZwKCvjh2_wSy6Jqhnc" name="attached_ephys_element">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</diagram><diagram id="g281dlesTKac3UB7zxho" name="attached_imaging_element">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</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"><defs/><g><image x="10.5" y="29.5" width="815.68" height="850" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBoZWlnaHQ9IjYzOSIgdmlld0JveD0iMCAwIDYxMy41IDYzOSIgd2lkdGg9IjYxMy41Ij4mI3hhOzxnIGNsYXNzPSJncmFwaCIgaWQ9ImdyYXBoMCIgdHJhbnNmb3JtPSJzY2FsZSgxIDEpIHJvdGF0ZSgwKSB0cmFuc2xhdGUoNCA2MzUpIj4mI3hhOzx0aXRsZT4lMzwvdGl0bGU+JiN4YTs8cG9seWdvbiBmaWxsPSJ3aGl0ZSIgcG9pbnRzPSItNCw0IC00LC02MzUgNjA5LjUsLTYzNSA2MDkuNSw0IC00LDQiIHN0cm9rZT0ibm9uZSIvPiYjeGE7PCEtLSAxIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMSI+PHRpdGxlPjE8L3RpdGxlPiYjeGE7PGVsbGlwc2UgY3g9Ijk4LjUiIGN5PSItMjY3LjUiIGZpbGw9IiNmZjg4MDAiIGZpbGwtb3BhY2l0eT0iMC41MDE5NjEiIHJ4PSIyIiByeT0iMiIgc3Ryb2tlPSIjZmY4ODAwIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NjEiLz4mI3hhOzx0ZXh0IGZpbGw9IiNmZjg4MDAiIGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxLjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSI5OC41IiB5PSItMjY3LjIiPjE8L3RleHQ+JiN4YTs8L2c+JiN4YTs8IS0tIFNlZ21lbnRhdGlvbi5NYXNrIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMjIiPjx0aXRsZT5TZWdtZW50YXRpb24uTWFzazwvdGl0bGU+JiN4YTs8ZyBpZD0iYV9ub2RlMjIiPjxhIHhsaW5rOnRpdGxlPSLihpIgU2VnbWVudGF0aW9uJiMxMDttYXNrIMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgJiMxMDstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0mIzEwO+KGkiBDaGFubmVsLnByb2ooc2VnX2NoYW5uZWw9JnF1b3Q7Y2hhbm5lbCZxdW90OykmIzEwO21hc2tfbnBpeCDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgJiMxMDttYXNrX2NlbnRlcl94IMKgwqDCoMKgwqDCoMKgJiMxMDttYXNrX2NlbnRlcl95IMKgwqDCoMKgwqDCoMKgJiMxMDttYXNrX2NlbnRlcl96IMKgwqDCoMKgwqDCoMKgJiMxMDttYXNrX3hwaXggwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7bWFza195cGl4IMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO21hc2tfenBpeCDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgJiMxMDttYXNrX3dlaWdodHMgwqDCoMKgwqDCoMKgwqDCoCYjMTA7Ij4mI3hhOzxwb2x5Z29uIGZpbGw9Im5vbmUiIHBvaW50cz0iMzkxLjUsLTE0NCAyODcuNSwtMTQ0IDI4Ny41LC0xMjUgMzkxLjUsLTEyNSAzOTEuNSwtMTQ0IiBzdHJva2U9Im5vbmUiLz4mI3hhOzx0ZXh0IGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxMC4wMCIgdGV4dC1hbmNob3I9InN0YXJ0IiB0ZXh0LWRlY29yYXRpb249InVuZGVybGluZSIgeD0iMjk1LjUiIHk9Ii0xMzMiPlNlZ21lbnRhdGlvbi5NYXNrPC90ZXh0PiYjeGE7PC9hPiYjeGE7PC9nPiYjeGE7PC9nPiYjeGE7PCEtLSAxJiM0NTsmZ3Q7U2VnbWVudGF0aW9uLk1hc2sgLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UxIj48dGl0bGU+MS0mZ3Q7U2VnbWVudGF0aW9uLk1hc2s8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTk4LjY0NzIsLTI2NS4xNDdDOTkuNjQ2MiwtMjU2LjY0NyAxMDYuMjA3LC0yMTAuMjY5IDEzMi41LC0xODggMTgzLjE3LC0xNDUuMDg2IDIxMy4xOTIsLTE2OC41MzkgMjc3LjUsLTE1MiAyODcuMzE3LC0xNDkuNDc1IDI5Ny45ODgsLTE0Ni42NjEgMzA3LjY2NywtMTQ0LjA4MSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtZGFzaGFycmF5PSI1LDIiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIDAgLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGUyIj48dGl0bGU+MDwvdGl0bGU+JiN4YTs8ZWxsaXBzZSBjeD0iNjYuNSIgY3k9Ii0yMDEiIGZpbGw9IiNmZjg4MDAiIGZpbGwtb3BhY2l0eT0iMC41MDE5NjEiIHJ4PSIyIiByeT0iMiIgc3Ryb2tlPSIjZmY4ODAwIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NjEiLz4mI3hhOzx0ZXh0IGZpbGw9IiNmZjg4MDAiIGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxLjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSI2Ni41IiB5PSItMjAwLjciPjA8L3RleHQ+JiN4YTs8L2c+JiN4YTs8IS0tIEZsdW9yZXNjZW5jZS5UcmFjZSAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTE5Ij48dGl0bGU+Rmx1b3Jlc2NlbmNlLlRyYWNlPC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGUxOSI+PGEgeGxpbms6dGl0bGU9IuKGkiBGbHVvcmVzY2VuY2UmIzEwO+KGkiBTZWdtZW50YXRpb24uTWFzayYjMTA74oaSIENoYW5uZWwucHJvaihmbHVvX2NoYW5uZWw9JnF1b3Q7Y2hhbm5lbCZxdW90OykmIzEwOy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSYjMTA7Zmx1b3Jlc2NlbmNlIMKgwqDCoMKgwqDCoMKgwqAmIzEwO25ldXJvcGlsX2ZsdW9yZXNjZW5jZT1udWxsICYjMTA7Ij4mI3hhOzxwb2x5Z29uIGZpbGw9Im5vbmUiIHBvaW50cz0iMjc2LjUsLTc3LjUgMTc0LjUsLTc3LjUgMTc0LjUsLTU4LjUgMjc2LjUsLTU4LjUgMjc2LjUsLTc3LjUiIHN0cm9rZT0ibm9uZSIvPiYjeGE7PHRleHQgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEwLjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyMjUuNSIgeT0iLTY1LjUiPkZsdW9yZXNjZW5jZS5UcmFjZTwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gMCYjNDU7Jmd0O0ZsdW9yZXNjZW5jZS5UcmFjZSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTIiPjx0aXRsZT4wLSZndDtGbHVvcmVzY2VuY2UuVHJhY2U8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTY3LjgxNzIsLTE5OS4xMzdDNzQuNzk1OCwtMTk0LjU1IDEwOC4wODgsLTE3Mi40NTkgMTMzLjUsLTE1MiAxMzUuNjM2LC0xNTAuMjggMTkyLjc4OSwtOTguNTk1NCAyMTUuOTE5LC03Ny42Njk2IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIDIgLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGUzIj48dGl0bGU+MjwvdGl0bGU+JiN4YTs8ZWxsaXBzZSBjeD0iMTM2LjUiIGN5PSItMzM0IiBmaWxsPSIjZmY4ODAwIiBmaWxsLW9wYWNpdHk9IjAuNTAxOTYxIiByeD0iMiIgcnk9IjIiIHN0cm9rZT0iI2ZmODgwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuNTAxOTYxIi8+JiN4YTs8dGV4dCBmaWxsPSIjZmY4ODAwIiBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMS4wMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTM2LjUiIHk9Ii0zMzMuNyI+MjwvdGV4dD4mI3hhOzwvZz4mI3hhOzwhLS0gTW90aW9uQ29ycmVjdGlvbiAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTQiPjx0aXRsZT5Nb3Rpb25Db3JyZWN0aW9uPC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGU0Ij48YSB4bGluazp0aXRsZT0i4oaSIFByb2Nlc3NpbmcmIzEwOy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSYjMTA74oaSIENoYW5uZWwucHJvaihtY19jaGFubmVsPSZxdW90O2NoYW5uZWwmcXVvdDspJiMxMDsiPiYjeGE7PGVsbGlwc2UgY3g9IjIzMC41IiBjeT0iLTI2Ny41IiBmaWxsPSIjMDAwMDdmIiBmaWxsLW9wYWNpdHk9IjAuMjUwOTgwIiByeD0iNjYuNzUwMiIgcnk9IjE3LjUiIHN0cm9rZT0iIzAwMDA3ZiIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIi8+JiN4YTs8dGV4dCBmaWxsPSIjMDAwMDdmIiBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTIuMDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjIzMC41IiB5PSItMjY0LjQiPk1vdGlvbkNvcnJlY3Rpb248L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIDImIzQ1OyZndDtNb3Rpb25Db3JyZWN0aW9uIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlMyI+PHRpdGxlPjItJmd0O01vdGlvbkNvcnJlY3Rpb248L3RpdGxlPiYjeGE7PHBhdGggZD0iTTEzNy41NTYsLTMzMi4yNzZDMTQ0LjMzMywtMzI3LjYyNSAxODIuMjQzLC0zMDEuNjEzIDIwNy41MDUsLTI4NC4yNzgiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWRhc2hhcnJheT0iNSwyIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBNb3Rpb25Db3JyZWN0aW9uLlN1bW1hcnkgLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGU5Ij48dGl0bGU+TW90aW9uQ29ycmVjdGlvbi5TdW1tYXJ5PC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGU5Ij48YSB4bGluazp0aXRsZT0i4oaSIE1vdGlvbkNvcnJlY3Rpb24mIzEwO+KGkiBTY2FuSW5mby5GaWVsZCYjMTA7LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJiMxMDtyZWZfaW1hZ2UgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7YXZlcmFnZV9pbWFnZSDCoMKgwqDCoMKgwqDCoCYjMTA7Y29ycmVsYXRpb25faW1hZ2U9bnVsbCAmIzEwO21heF9wcm9qX2ltYWdlPW51bGwgwqAmIzEwOyI+JiN4YTs8cG9seWdvbiBmaWxsPSJub25lIiBwb2ludHM9IjI3NywtMjEwLjUgMTQyLC0yMTAuNSAxNDIsLTE5MS41IDI3NywtMTkxLjUgMjc3LC0yMTAuNSIgc3Ryb2tlPSJub25lIi8+JiN4YTs8dGV4dCBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTAuMDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjIwOS41IiB5PSItMTk4LjUiPk1vdGlvbkNvcnJlY3Rpb24uU3VtbWFyeTwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gTW90aW9uQ29ycmVjdGlvbiYjNDU7Jmd0O01vdGlvbkNvcnJlY3Rpb24uU3VtbWFyeSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTQiPjx0aXRsZT5Nb3Rpb25Db3JyZWN0aW9uLSZndDtNb3Rpb25Db3JyZWN0aW9uLlN1bW1hcnk8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTIyNS4wOTIsLTI0OS44OUMyMjEuMDA5LC0yMzcuMzQ4IDIxNS41NywtMjIwLjY0MyAyMTIuMzEzLC0yMTAuNjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gU2VnbWVudGF0aW9uIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMjMiPjx0aXRsZT5TZWdtZW50YXRpb248L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTIzIj48YSB4bGluazp0aXRsZT0i4oaSIE1vdGlvbkNvcnJlY3Rpb24mIzEwO+KGkiBDdXJhdGlvbiYjMTA7Ij4mI3hhOzxlbGxpcHNlIGN4PSIzMzkuNSIgY3k9Ii0yMDEiIGZpbGw9IiNmZjAwMDAiIGZpbGwtb3BhY2l0eT0iMC4xMjU0OTAiIHJ4PSIxMyIgcnk9IjEzIiBzdHJva2U9IiNmZjAwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjEyNTQ5MCIvPiYjeGE7PHRleHQgZmlsbD0iIzdmMDAwMCIgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEyLjAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIzMzkuNSIgeT0iLTE5Ny45Ij5TZWdtZW50YXRpb248L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIE1vdGlvbkNvcnJlY3Rpb24mIzQ1OyZndDtTZWdtZW50YXRpb24gLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2U1Ij48dGl0bGU+TW90aW9uQ29ycmVjdGlvbi0mZ3Q7U2VnbWVudGF0aW9uPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0yNTYuMzMxLC0yNTEuMjE0QzI3OS4zNzMsLTIzNy41OCAzMTEuOTcyLC0yMTguMjkgMzI4LjY4OSwtMjA4LjM5NyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBNYXNrQ2xhc3NpZmljYXRpb24gLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGU1Ij48dGl0bGU+TWFza0NsYXNzaWZpY2F0aW9uPC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGU1Ij48YSB4bGluazp0aXRsZT0i4oaSIFNlZ21lbnRhdGlvbiYjMTA74oaSIE1hc2tDbGFzc2lmaWNhdGlvbk1ldGhvZCYjMTA7Ij4mI3hhOzxlbGxpcHNlIGN4PSI0NjcuNSIgY3k9Ii0xMzQuNSIgZmlsbD0iI2ZmMDAwMCIgZmlsbC1vcGFjaXR5PSIwLjEyNTQ5MCIgcng9IjEzIiByeT0iMTMiIHN0cm9rZT0iI2ZmMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMTI1NDkwIi8+JiN4YTs8dGV4dCBmaWxsPSIjN2YwMDAwIiBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTIuMDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjQ2Ny41IiB5PSItMTMxLjQiPk1hc2tDbGFzc2lmaWNhdGlvbjwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gTWFza0NsYXNzaWZpY2F0aW9uLk1hc2tUeXBlIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMjAiPjx0aXRsZT5NYXNrQ2xhc3NpZmljYXRpb24uTWFza1R5cGU8L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTIwIj48YSB4bGluazp0aXRsZT0i4oaSIE1hc2tDbGFzc2lmaWNhdGlvbiYjMTA74oaSIFNlZ21lbnRhdGlvbi5NYXNrJiMxMDstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0mIzEwO+KGkiBNYXNrVHlwZSYjMTA7Y29uZmlkZW5jZT1udWxsIMKgwqDCoMKgwqAmIzEwOyI+JiN4YTs8cG9seWdvbiBmaWxsPSJub25lIiBwb2ludHM9IjU0MC41LC03Ny41IDM5NC41LC03Ny41IDM5NC41LC01OC41IDU0MC41LC01OC41IDU0MC41LC03Ny41IiBzdHJva2U9Im5vbmUiLz4mI3hhOzx0ZXh0IGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxMC4wMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iNDY3LjUiIHk9Ii02NS41Ij5NYXNrQ2xhc3NpZmljYXRpb24uTWFza1R5cGU8L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIE1hc2tDbGFzc2lmaWNhdGlvbiYjNDU7Jmd0O01hc2tDbGFzc2lmaWNhdGlvbi5NYXNrVHlwZSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTYiPjx0aXRsZT5NYXNrQ2xhc3NpZmljYXRpb24tJmd0O01hc2tDbGFzc2lmaWNhdGlvbi5NYXNrVHlwZTwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNNDY3LjUsLTEyMS4zNjhDNDY3LjUsLTEwOC42NjkgNDY3LjUsLTg5LjAzOTggNDY3LjUsLTc3Ljc2OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBFcXVpcG1lbnQgLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGU2Ij48dGl0bGU+RXF1aXBtZW50PC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGU2Ij48YSB4bGluazp0aXRsZT0ic2Nhbm5lciDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7Ij4mI3hhOzxwb2x5Z29uIGZpbGw9IiMwMGZmMDAiIGZpbGwtb3BhY2l0eT0iMC4xODgyMzUiIHBvaW50cz0iMTE1LC01NjAgNDIsLTU2MCA0MiwtNTI1IDExNSwtNTI1IDExNSwtNTYwIiBzdHJva2U9IiMwMGZmMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjE4ODIzNSIvPiYjeGE7PHRleHQgZmlsbD0iZGFya2dyZWVuIiBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTIuMDAiIHRleHQtYW5jaG9yPSJzdGFydCIgdGV4dC1kZWNvcmF0aW9uPSJ1bmRlcmxpbmUiIHg9IjUwIiB5PSItNTQwLjQiPkVxdWlwbWVudDwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gU2NhbiAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTE0Ij48dGl0bGU+U2NhbjwvdGl0bGU+JiN4YTs8ZyBpZD0iYV9ub2RlMTQiPjxhIHhsaW5rOnRpdGxlPSLihpIgU2Vzc2lvbiYjMTA7c2Nhbl9pZCDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJiMxMDvihpIgRXF1aXBtZW50JiMxMDvihpIgQWNxdWlzaXRpb25Tb2Z0d2FyZSYjMTA7c2Nhbl9ub3Rlcz0mcXVvdDsmcXVvdDsgwqDCoMKgwqDCoMKgwqAmIzEwOyI+JiN4YTs8cG9seWdvbiBmaWxsPSIjMDBmZjAwIiBmaWxsLW9wYWNpdHk9IjAuMTg4MjM1IiBwb2ludHM9IjIyNC41LC00ODkgMTgwLjUsLTQ4OSAxODAuNSwtNDU0IDIyNC41LC00NTQgMjI0LjUsLTQ4OSIgc3Ryb2tlPSIjMDBmZjAwIiBzdHJva2Utb3BhY2l0eT0iMC4xODgyMzUiLz4mI3hhOzx0ZXh0IGZpbGw9ImRhcmtncmVlbiIgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEyLjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIHRleHQtZGVjb3JhdGlvbj0idW5kZXJsaW5lIiB4PSIxODguNSIgeT0iLTQ2OS40Ij5TY2FuPC90ZXh0PiYjeGE7PC9hPiYjeGE7PC9nPiYjeGE7PC9nPiYjeGE7PCEtLSBFcXVpcG1lbnQmIzQ1OyZndDtTY2FuIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlNyI+PHRpdGxlPkVxdWlwbWVudC0mZ3Q7U2NhbjwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMTA4LjIwMSwtNTI0Ljk3M0MxMzAuNDEyLC01MTIuNjEzIDE2MC4yOTYsLTQ5NS45ODQgMTgwLjQyLC00ODQuNzg3IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1kYXNoYXJyYXk9IjUsMiIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gUHJvY2Vzc2luZ1Rhc2sgLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGU3Ij48dGl0bGU+UHJvY2Vzc2luZ1Rhc2s8L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTciPjxhIHhsaW5rOnRpdGxlPSLihpIgU2NhbiYjMTA74oaSIFByb2Nlc3NpbmdQYXJhbVNldCYjMTA7LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJiMxMDtwcm9jZXNzaW5nX291dHB1dF9kaXIgJiMxMDt0YXNrX21vZGU9JnF1b3Q7bG9hZCZxdW90OyDCoMKgwqDCoCYjMTA7Ij4mI3hhOzxwb2x5Z29uIGZpbGw9IiMwMGZmMDAiIGZpbGwtb3BhY2l0eT0iMC4xODgyMzUiIHBvaW50cz0iMzc4LC00MTggMjc3LC00MTggMjc3LC0zODMgMzc4LC0zODMgMzc4LC00MTgiIHN0cm9rZT0iIzAwZmYwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMTg4MjM1Ii8+JiN4YTs8dGV4dCBmaWxsPSJkYXJrZ3JlZW4iIGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxMi4wMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMzI3LjUiIHk9Ii0zOTcuNCI+UHJvY2Vzc2luZ1Rhc2s8L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIFByb2Nlc3NpbmcgLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGUxMiI+PHRpdGxlPlByb2Nlc3Npbmc8L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTEyIj48YSB4bGluazp0aXRsZT0i4oaSIFByb2Nlc3NpbmdUYXNrJiMxMDstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0mIzEwO3Byb2Nlc3NpbmdfdGltZSDCoMKgwqDCoMKgJiMxMDsiPiYjeGE7PGVsbGlwc2UgY3g9IjMyNy41IiBjeT0iLTMzNCIgZmlsbD0iI2ZmMDAwMCIgZmlsbC1vcGFjaXR5PSIwLjEyNTQ5MCIgcng9IjEzIiByeT0iMTMiIHN0cm9rZT0iI2ZmMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMTI1NDkwIi8+JiN4YTs8dGV4dCBmaWxsPSIjN2YwMDAwIiBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTIuMDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjMyNy41IiB5PSItMzMwLjkiPlByb2Nlc3Npbmc8L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIFByb2Nlc3NpbmdUYXNrJiM0NTsmZ3Q7UHJvY2Vzc2luZyAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTgiPjx0aXRsZT5Qcm9jZXNzaW5nVGFzay0mZ3Q7UHJvY2Vzc2luZzwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMzI3LjUsLTM4Mi44OUMzMjcuNSwtMzcxLjY5MiAzMjcuNSwtMzU3LjE3NSAzMjcuNSwtMzQ3LjA3MyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMiIvPiYjeGE7PC9nPiYjeGE7PCEtLSBNYXNrVHlwZSAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTgiPjx0aXRsZT5NYXNrVHlwZTwvdGl0bGU+JiN4YTs8ZyBpZD0iYV9ub2RlOCI+PGEgeGxpbms6dGl0bGU9Im1hc2tfdHlwZSDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgJiMxMDsiPiYjeGE7PHBvbHlnb24gZmlsbD0iIzAwMDAwMCIgZmlsbC1vcGFjaXR5PSIwLjEyNTQ5MCIgcG9pbnRzPSI2MDUuNSwtMTUyIDU0My41LC0xNTIgNTQzLjUsLTExNyA2MDUuNSwtMTE3IDYwNS41LC0xNTIiIHN0cm9rZT0ibm9uZSIvPiYjeGE7PHRleHQgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEwLjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIHRleHQtZGVjb3JhdGlvbj0idW5kZXJsaW5lIiB4PSI1NTEuNSIgeT0iLTEzMyI+TWFza1R5cGU8L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIE1hc2tUeXBlJiM0NTsmZ3Q7TWFza0NsYXNzaWZpY2F0aW9uLk1hc2tUeXBlIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlOSI+PHRpdGxlPk1hc2tUeXBlLSZndDtNYXNrQ2xhc3NpZmljYXRpb24uTWFza1R5cGU8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTU0Ni45NDUsLTExNi44OUM1MjYuMTQsLTEwNC4zNDggNDk4LjQyNywtODcuNjQzIDQ4MS44MzMsLTc3LjY0MDIiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWRhc2hhcnJheT0iNSwyIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBGbHVvcmVzY2VuY2UgLS0+JiN4YTs8ZyBjbGFzcz0ibm9kZSIgaWQ9Im5vZGUxMCI+PHRpdGxlPkZsdW9yZXNjZW5jZTwvdGl0bGU+JiN4YTs8ZyBpZD0iYV9ub2RlMTAiPjxhIHhsaW5rOnRpdGxlPSLihpIgU2VnbWVudGF0aW9uJiMxMDsiPiYjeGE7PGVsbGlwc2UgY3g9IjIyNS41IiBjeT0iLTEzNC41IiBmaWxsPSIjZmYwMDAwIiBmaWxsLW9wYWNpdHk9IjAuMTI1NDkwIiByeD0iMTMiIHJ5PSIxMyIgc3Ryb2tlPSIjZmYwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4xMjU0OTAiLz4mI3hhOzx0ZXh0IGZpbGw9IiM3ZjAwMDAiIGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxMi4wMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjI1LjUiIHk9Ii0xMzEuNCI+Rmx1b3Jlc2NlbmNlPC90ZXh0PiYjeGE7PC9hPiYjeGE7PC9nPiYjeGE7PC9nPiYjeGE7PCEtLSBGbHVvcmVzY2VuY2UmIzQ1OyZndDtGbHVvcmVzY2VuY2UuVHJhY2UgLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UxMCI+PHRpdGxlPkZsdW9yZXNjZW5jZS0mZ3Q7Rmx1b3Jlc2NlbmNlLlRyYWNlPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0yMjUuNSwtMTIxLjM2OEMyMjUuNSwtMTA4LjY2OSAyMjUuNSwtODkuMDM5OCAyMjUuNSwtNzcuNzY4IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIEFjdGl2aXR5IC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMjEiPjx0aXRsZT5BY3Rpdml0eTwvdGl0bGU+JiN4YTs8ZyBpZD0iYV9ub2RlMjEiPjxhIHhsaW5rOnRpdGxlPSLihpIgRmx1b3Jlc2NlbmNlJiMxMDvihpIgQWN0aXZpdHlFeHRyYWN0aW9uTWV0aG9kJiMxMDsiPiYjeGE7PGVsbGlwc2UgY3g9IjEyOS41IiBjeT0iLTY4IiBmaWxsPSIjZmYwMDAwIiBmaWxsLW9wYWNpdHk9IjAuMTI1NDkwIiByeD0iMTMiIHJ5PSIxMyIgc3Ryb2tlPSIjZmYwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4xMjU0OTAiLz4mI3hhOzx0ZXh0IGZpbGw9IiM3ZjAwMDAiIGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxMi4wMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTI5LjUiIHk9Ii02NC45Ij5BY3Rpdml0eTwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gRmx1b3Jlc2NlbmNlJiM0NTsmZ3Q7QWN0aXZpdHkgLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UxMSI+PHRpdGxlPkZsdW9yZXNjZW5jZS0mZ3Q7QWN0aXZpdHk8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTIxNC44NiwtMTI2LjM1MUMxOTYuMzA5LC0xMTMuODg3IDE1OC4yNzUsLTg4LjMzMyAxMzkuODk2LC03NS45ODUiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gQWNxdWlzaXRpb25Tb2Z0d2FyZSAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTExIj48dGl0bGU+QWNxdWlzaXRpb25Tb2Z0d2FyZTwvdGl0bGU+JiN4YTs8ZyBpZD0iYV9ub2RlMTEiPjxhIHhsaW5rOnRpdGxlPSJhY3Ffc29mdHdhcmUgwqDCoMKgwqDCoMKgwqDCoCYjMTA7Ij4mI3hhOzxwb2x5Z29uIGZpbGw9IiMwMDAwMDAiIGZpbGwtb3BhY2l0eT0iMC4xMjU0OTAiIHBvaW50cz0iMjM1LjUsLTU2MCAxMzMuNSwtNTYwIDEzMy41LC01MjUgMjM1LjUsLTUyNSAyMzUuNSwtNTYwIiBzdHJva2U9Im5vbmUiLz4mI3hhOzx0ZXh0IGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxMC4wMCIgdGV4dC1hbmNob3I9InN0YXJ0IiB0ZXh0LWRlY29yYXRpb249InVuZGVybGluZSIgeD0iMTQxLjUiIHk9Ii01NDEiPkFjcXVpc2l0aW9uU29mdHdhcmU8L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIEFjcXVpc2l0aW9uU29mdHdhcmUmIzQ1OyZndDtTY2FuIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlMTIiPjx0aXRsZT5BY3F1aXNpdGlvblNvZnR3YXJlLSZndDtTY2FuPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0xODguODU3LC01MjQuNzk3QzE5MS42ODcsLTUxMy45NDkgMTk1LjM2MSwtNDk5Ljg2NyAxOTguMTgzLC00ODkuMDQ5IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1kYXNoYXJyYXk9IjUsMiIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gUHJvY2Vzc2luZyYjNDU7Jmd0O01vdGlvbkNvcnJlY3Rpb24gLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UxMyI+PHRpdGxlPlByb2Nlc3NpbmctJmd0O01vdGlvbkNvcnJlY3Rpb248L3RpdGxlPiYjeGE7PHBhdGggZD0iTTMxNy4xMTksLTMyNi4wOTdDMzAyLjE2NCwtMzE2LjE1MyAyNzQuMTQ1LC0yOTcuNTIyIDI1NC4wMiwtMjg0LjEzOSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMiIvPiYjeGE7PC9nPiYjeGE7PCEtLSBDdXJhdGlvbiAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTE2Ij48dGl0bGU+Q3VyYXRpb248L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTE2Ij48YSB4bGluazp0aXRsZT0i4oaSIFByb2Nlc3NpbmcmIzEwO2N1cmF0aW9uX2lkIMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJiMxMDtjdXJhdGlvbl90aW1lIMKgwqDCoMKgwqDCoMKgJiMxMDtjdXJhdGlvbl9vdXRwdXRfZGlyIMKgJiMxMDttYW51YWxfY3VyYXRpb24gwqDCoMKgwqDCoCYjMTA7Y3VyYXRpb25fbm90ZT0mcXVvdDsmcXVvdDsgwqDCoMKgwqAmIzEwOyI+JiN4YTs8cG9seWdvbiBmaWxsPSIjMDBmZjAwIiBmaWxsLW9wYWNpdHk9IjAuMTg4MjM1IiBwb2ludHM9IjM3NiwtMjg1IDMxNSwtMjg1IDMxNSwtMjUwIDM3NiwtMjUwIDM3NiwtMjg1IiBzdHJva2U9IiMwMGZmMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjE4ODIzNSIvPiYjeGE7PHRleHQgZmlsbD0iZGFya2dyZWVuIiBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTIuMDAiIHRleHQtYW5jaG9yPSJzdGFydCIgdGV4dC1kZWNvcmF0aW9uPSJ1bmRlcmxpbmUiIHg9IjMyMyIgeT0iLTI2NS40Ij5DdXJhdGlvbjwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gUHJvY2Vzc2luZyYjNDU7Jmd0O0N1cmF0aW9uIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlMTQiPjx0aXRsZT5Qcm9jZXNzaW5nLSZndDtDdXJhdGlvbjwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMzMwLjgwMSwtMzIxLjE3QzMzMy42MDMsLTMxMS4xMzIgMzM3LjY1NSwtMjk2LjYxMSAzNDAuNzk2LC0yODUuMzU3IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIEFjdGl2aXR5RXh0cmFjdGlvbk1ldGhvZCAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTEzIj48dGl0bGU+QWN0aXZpdHlFeHRyYWN0aW9uTWV0aG9kPC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGUxMyI+PGEgeGxpbms6dGl0bGU9ImV4dHJhY3Rpb25fbWV0aG9kIMKgwqDCoCYjMTA7Ij4mI3hhOzxwb2x5Z29uIGZpbGw9IiMwMDAwMDAiIGZpbGwtb3BhY2l0eT0iMC4xMjU0OTAiIHBvaW50cz0iMTI1LC0xNTIgMCwtMTUyIDAsLTExNyAxMjUsLTExNyAxMjUsLTE1MiIgc3Ryb2tlPSJub25lIi8+JiN4YTs8dGV4dCBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTAuMDAiIHRleHQtYW5jaG9yPSJzdGFydCIgdGV4dC1kZWNvcmF0aW9uPSJ1bmRlcmxpbmUiIHg9IjgiIHk9Ii0xMzMiPkFjdGl2aXR5RXh0cmFjdGlvbk1ldGhvZDwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gQWN0aXZpdHlFeHRyYWN0aW9uTWV0aG9kJiM0NTsmZ3Q7QWN0aXZpdHkgLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UxNSI+PHRpdGxlPkFjdGl2aXR5RXh0cmFjdGlvbk1ldGhvZC0mZ3Q7QWN0aXZpdHk8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTc5Ljc1NDIsLTExNi44OUM5Mi43ODE4LC0xMDQuMzQ4IDExMC4xMzQsLTg3LjY0MyAxMjAuNTI1LC03Ny42NDAyIiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIFNjYW4mIzQ1OyZndDtQcm9jZXNzaW5nVGFzayAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTE2Ij48dGl0bGU+U2Nhbi0mZ3Q7UHJvY2Vzc2luZ1Rhc2s8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTIyNC41NiwtNDU4LjMyM0MyNDQuNzQ1LC00NDcuMTgxIDI3NC43OTMsLTQzMC41OTQgMjk3LjIyMSwtNDE4LjIxNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBTY2FuSW5mbyAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTI0Ij48dGl0bGU+U2NhbkluZm88L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTI0Ij48YSB4bGluazp0aXRsZT0i4oaSIFNjYW4mIzEwOy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSYjMTA7bmZpZWxkcyDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7bmNoYW5uZWxzIMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO25kZXB0aHMgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO25mcmFtZXMgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO25yb2lzIMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7eCDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7eSDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7eiDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7ZnBzIMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO2JpZGlyZWN0aW9uYWwgwqDCoMKgwqDCoMKgwqAmIzEwO3VzZWNzX3Blcl9saW5lPW51bGwgwqAmIzEwO2ZpbGxfZnJhY3Rpb249bnVsbCDCoMKgJiMxMDsiPiYjeGE7PGVsbGlwc2UgY3g9IjIwMi41IiBjeT0iLTQwMC41IiBmaWxsPSIjMDAwMDdmIiBmaWxsLW9wYWNpdHk9IjAuMjUwOTgwIiByeD0iMzguNTAxNiIgcnk9IjE3LjUiIHN0cm9rZT0iIzAwMDA3ZiIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIi8+JiN4YTs8dGV4dCBmaWxsPSIjMDAwMDdmIiBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTIuMDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjIwMi41IiB5PSItMzk3LjQiPlNjYW5JbmZvPC90ZXh0PiYjeGE7PC9hPiYjeGE7PC9nPiYjeGE7PC9nPiYjeGE7PCEtLSBTY2FuJiM0NTsmZ3Q7U2NhbkluZm8gLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UxNyI+PHRpdGxlPlNjYW4tJmd0O1NjYW5JbmZvPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0yMDIuNSwtNDUzLjc5N0MyMDIuNSwtNDQyLjk0OSAyMDIuNSwtNDI4Ljg2NyAyMDIuNSwtNDE4LjA0OSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMiIvPiYjeGE7PC9nPiYjeGE7PCEtLSBTdWJqZWN0IC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMTUiPjx0aXRsZT5TdWJqZWN0PC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGUxNSI+PGEgeGxpbms6dGl0bGU9InN1YmplY3QgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwOy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSYjMTA7c2V4IMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO3N1YmplY3RfYmlydGhfZGF0ZSDCoMKgJiMxMDtzdWJqZWN0X2Rlc2NyaXB0aW9uPSZxdW90OyZxdW90OyAmIzEwOyI+JiN4YTs8cG9seWdvbiBmaWxsPSIjMDBmZjAwIiBmaWxsLW9wYWNpdHk9IjAuMTg4MjM1IiBwb2ludHM9IjMxMS41LC02MzEgMjU1LjUsLTYzMSAyNTUuNSwtNTk2IDMxMS41LC01OTYgMzExLjUsLTYzMSIgc3Ryb2tlPSIjMDBmZjAwIiBzdHJva2Utb3BhY2l0eT0iMC4xODgyMzUiLz4mI3hhOzx0ZXh0IGZpbGw9ImRhcmtncmVlbiIgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEyLjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIHRleHQtZGVjb3JhdGlvbj0idW5kZXJsaW5lIiB4PSIyNjMuNSIgeT0iLTYxMS40Ij5TdWJqZWN0PC90ZXh0PiYjeGE7PC9hPiYjeGE7PC9nPiYjeGE7PC9nPiYjeGE7PCEtLSBTZXNzaW9uIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMjUiPjx0aXRsZT5TZXNzaW9uPC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGUyNSI+PGEgeGxpbms6dGl0bGU9IuKGkiBTdWJqZWN0JiMxMDtzZXNzaW9uX2RhdGV0aW1lIMKgwqDCoMKgJiMxMDsiPiYjeGE7PHBvbHlnb24gZmlsbD0iIzAwZmYwMCIgZmlsbC1vcGFjaXR5PSIwLjE4ODIzNSIgcG9pbnRzPSIzMTMsLTU2MCAyNTQsLTU2MCAyNTQsLTUyNSAzMTMsLTUyNSAzMTMsLTU2MCIgc3Ryb2tlPSIjMDBmZjAwIiBzdHJva2Utb3BhY2l0eT0iMC4xODgyMzUiLz4mI3hhOzx0ZXh0IGZpbGw9ImRhcmtncmVlbiIgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEyLjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIHRleHQtZGVjb3JhdGlvbj0idW5kZXJsaW5lIiB4PSIyNjIiIHk9Ii01NDAuNCI+U2Vzc2lvbjwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gU3ViamVjdCYjNDU7Jmd0O1Nlc3Npb24gLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UxOCI+PHRpdGxlPlN1YmplY3QtJmd0O1Nlc3Npb248L3RpdGxlPiYjeGE7PHBhdGggZD0iTTI4My41LC01OTUuNzk3QzI4My41LC01ODQuOTQ5IDI4My41LC01NzAuODY3IDI4My41LC01NjAuMDQ5IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIEN1cmF0aW9uJiM0NTsmZ3Q7U2VnbWVudGF0aW9uIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlMTkiPjx0aXRsZT5DdXJhdGlvbi0mZ3Q7U2VnbWVudGF0aW9uPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0zNDMuOTU1LC0yNDkuODlDMzQyLjkxMywtMjM4LjY5MiAzNDEuNTYzLC0yMjQuMTc1IDM0MC42MjMsLTIxNC4wNzMiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjIiLz4mI3hhOzwvZz4mI3hhOzwhLS0gUHJvY2Vzc2luZ1BhcmFtU2V0IC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMTciPjx0aXRsZT5Qcm9jZXNzaW5nUGFyYW1TZXQ8L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTE3Ij48YSB4bGluazp0aXRsZT0icGFyYW1zZXRfaWR4IMKgwqDCoMKgwqDCoMKgwqAmIzEwOy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSYjMTA74oaSIFByb2Nlc3NpbmdNZXRob2QmIzEwO3BhcmFtc2V0X2Rlc2MgwqDCoMKgwqDCoMKgwqAmIzEwO3BhcmFtX3NldF9oYXNoIMKgwqDCoMKgwqDCoCYjMTA7cGFyYW1zIMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO1VOSVFVRSBJTkRFWCAocGFyYW1fc2V0X2hhc2gpJiMxMDsiPiYjeGE7PHBvbHlnb24gZmlsbD0iIzAwMDAwMCIgZmlsbC1vcGFjaXR5PSIwLjEyNTQ5MCIgcG9pbnRzPSI0MTcsLTQ4OSAzMDgsLTQ4OSAzMDgsLTQ1NCA0MTcsLTQ1NCA0MTcsLTQ4OSIgc3Ryb2tlPSJub25lIi8+JiN4YTs8dGV4dCBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTAuMDAiIHRleHQtYW5jaG9yPSJzdGFydCIgdGV4dC1kZWNvcmF0aW9uPSJ1bmRlcmxpbmUiIHg9IjMxNiIgeT0iLTQ3MCI+UHJvY2Vzc2luZ1BhcmFtU2V0PC90ZXh0PiYjeGE7PC9hPiYjeGE7PC9nPiYjeGE7PC9nPiYjeGE7PCEtLSBQcm9jZXNzaW5nUGFyYW1TZXQmIzQ1OyZndDtQcm9jZXNzaW5nVGFzayAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTIwIj48dGl0bGU+UHJvY2Vzc2luZ1BhcmFtU2V0LSZndDtQcm9jZXNzaW5nVGFzazwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMzU0LjAyNywtNDUzLjc5N0MzNDguNTI1LC00NDIuOTQ5IDM0MS4zODIsLTQyOC44NjcgMzM1Ljg5NSwtNDE4LjA0OSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBDaGFubmVsIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMTgiPjx0aXRsZT5DaGFubmVsPC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGUxOCI+PGEgeGxpbms6dGl0bGU9ImNoYW5uZWwgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwOyI+JiN4YTs8cG9seWdvbiBmaWxsPSIjMDAwMDAwIiBmaWxsLW9wYWNpdHk9IjAuMTI1NDkwIiBwb2ludHM9IjEyNS41LC00MTggNzMuNSwtNDE4IDczLjUsLTM4MyAxMjUuNSwtMzgzIDEyNS41LC00MTgiIHN0cm9rZT0ibm9uZSIvPiYjeGE7PHRleHQgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEwLjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIHRleHQtZGVjb3JhdGlvbj0idW5kZXJsaW5lIiB4PSI4MS41IiB5PSItMzk5Ij5DaGFubmVsPC90ZXh0PiYjeGE7PC9hPiYjeGE7PC9nPiYjeGE7PC9nPiYjeGE7PCEtLSBDaGFubmVsJiM0NTsmZ3Q7MSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTIyIj48dGl0bGU+Q2hhbm5lbC0mZ3Q7MTwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNOTkuMzczMywtMzgyLjkwOUM5OS4xMjMyLC0zNTAuMTM3IDk4LjU4MzMsLTI3OS40MTUgOTguNTA4NiwtMjY5LjYzMSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtZGFzaGFycmF5PSI1LDIiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIENoYW5uZWwmIzQ1OyZndDswIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlMjEiPjx0aXRsZT5DaGFubmVsLSZndDswPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik05Ni42ODY0LC0zODIuNjYxQzg5LjIwNDksLTMzNy44ODUgNjkuMjIwMiwtMjE4LjI4IDY2Ljc1MTMsLTIwMy41MDQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gQ2hhbm5lbCYjNDU7Jmd0OzIgLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UyMyI+PHRpdGxlPkNoYW5uZWwtJmd0OzI8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTEwOS4wMjgsLTM4Mi44OUMxMTguNjIxLC0zNjYuMTY4IDEzMi40NTksLTM0Mi4wNDQgMTM1Ljc2OSwtMzM2LjI3NCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtZGFzaGFycmF5PSI1LDIiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIEFjdGl2aXR5LlRyYWNlIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMjgiPjx0aXRsZT5BY3Rpdml0eS5UcmFjZTwvdGl0bGU+JiN4YTs8ZyBpZD0iYV9ub2RlMjgiPjxhIHhsaW5rOnRpdGxlPSLihpIgQWN0aXZpdHkmIzEwO+KGkiBGbHVvcmVzY2VuY2UuVHJhY2UmIzEwOy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSYjMTA7YWN0aXZpdHlfdHJhY2UgwqDCoMKgwqDCoMKgJiMxMDsiPiYjeGE7PHBvbHlnb24gZmlsbD0ibm9uZSIgcG9pbnRzPSIxNjcsLTE5IDkyLC0xOSA5MiwtMCAxNjcsLTAgMTY3LC0xOSIgc3Ryb2tlPSJub25lIi8+JiN4YTs8dGV4dCBmb250LWZhbWlseT0iYXJpYWwiIGZvbnQtc2l6ZT0iMTAuMDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjEyOS41IiB5PSItNyI+QWN0aXZpdHkuVHJhY2U8L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIEZsdW9yZXNjZW5jZS5UcmFjZSYjNDU7Jmd0O0FjdGl2aXR5LlRyYWNlIC0tPiYjeGE7PGcgY2xhc3M9ImVkZ2UiIGlkPSJlZGdlMjQiPjx0aXRsZT5GbHVvcmVzY2VuY2UuVHJhY2UtJmd0O0FjdGl2aXR5LlRyYWNlPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0yMTAuOTIsLTU4LjQxODlDMTkyLjg3NiwtNDcuNzk5NCAxNjIuMzI0LC0yOS44MTg1IDE0NC4yMTgsLTE5LjE2MTkiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gQWN0aXZpdHkmIzQ1OyZndDtBY3Rpdml0eS5UcmFjZSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTI1Ij48dGl0bGU+QWN0aXZpdHktJmd0O0FjdGl2aXR5LlRyYWNlPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0xMjkuNSwtNTQuNzQwM0MxMjkuNSwtNDMuOTY1MiAxMjkuNSwtMjguNTYyMyAxMjkuNSwtMTkuMDYzOSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBTZWdtZW50YXRpb24uTWFzayYjNDU7Jmd0O0ZsdW9yZXNjZW5jZS5UcmFjZSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTI2Ij48dGl0bGU+U2VnbWVudGF0aW9uLk1hc2stJmd0O0ZsdW9yZXNjZW5jZS5UcmFjZTwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMzI0LjEyNSwtMTI0LjgwMUMzMDIuMjQ2LC0xMTIuNDIyIDI2Mi4zNzYsLTg5Ljg2MzkgMjQwLjYzMSwtNzcuNTYwOSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBTZWdtZW50YXRpb24uTWFzayYjNDU7Jmd0O01hc2tDbGFzc2lmaWNhdGlvbi5NYXNrVHlwZSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTI3Ij48dGl0bGU+U2VnbWVudGF0aW9uLk1hc2stJmd0O01hc2tDbGFzc2lmaWNhdGlvbi5NYXNrVHlwZTwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMzU2LjQ5NywtMTI0LjkzNUMzODAuOTc5LC0xMTIuNTk4IDQyNS45MTEsLTg5Ljk1NjcgNDUwLjQzMiwtNzcuNjAwNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBTZWdtZW50YXRpb24mIzQ1OyZndDtNYXNrQ2xhc3NpZmljYXRpb24gLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UyOCI+PHRpdGxlPlNlZ21lbnRhdGlvbi0mZ3Q7TWFza0NsYXNzaWZpY2F0aW9uPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0zNTAuODU5LC0xOTQuMjc2QzM3NS4wODgsLTE4Mi4wNjcgNDMxLjYxMywtMTUzLjU4NCA0NTUuOTgyLC0xNDEuMzA0IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8IS0tIFNlZ21lbnRhdGlvbiYjNDU7Jmd0O0ZsdW9yZXNjZW5jZSAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTI5Ij48dGl0bGU+U2VnbWVudGF0aW9uLSZndDtGbHVvcmVzY2VuY2U8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTMyOC41NjksLTE5My44MTVDMzA2LjgzNSwtMTgxLjUxOCAyNTguNDA5LC0xNTQuMTIgMjM2LjU2NSwtMTQxLjc2MSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMiIvPiYjeGE7PC9nPiYjeGE7PCEtLSBTZWdtZW50YXRpb24mIzQ1OyZndDtTZWdtZW50YXRpb24uTWFzayAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTMwIj48dGl0bGU+U2VnbWVudGF0aW9uLSZndDtTZWdtZW50YXRpb24uTWFzazwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMzM5LjUsLTE4Ny44NjhDMzM5LjUsLTE3NS4xNjkgMzM5LjUsLTE1NS41NCAzMzkuNSwtMTQ0LjI2OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBTY2FuSW5mby5GaWVsZCAtLT4mI3hhOzxnIGNsYXNzPSJub2RlIiBpZD0ibm9kZTI2Ij48dGl0bGU+U2NhbkluZm8uRmllbGQ8L3RpdGxlPiYjeGE7PGcgaWQ9ImFfbm9kZTI2Ij48YSB4bGluazp0aXRsZT0i4oaSIFNjYW5JbmZvJiMxMDtmaWVsZF9pZHggwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJiMxMDtweF9oZWlnaHQgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCYjMTA7cHhfd2lkdGggwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgJiMxMDt1bV9oZWlnaHQ9bnVsbCDCoMKgwqDCoMKgwqAmIzEwO3VtX3dpZHRoPW51bGwgwqDCoMKgwqDCoMKgwqAmIzEwO2ZpZWxkX3ggwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO2ZpZWxkX3kgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO2ZpZWxkX3ogwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAmIzEwO2RlbGF5X2ltYWdlPW51bGwgwqDCoMKgwqAmIzEwOyI+JiN4YTs8cG9seWdvbiBmaWxsPSJub25lIiBwb2ludHM9IjI0MS41LC0zNDMuNSAxNjMuNSwtMzQzLjUgMTYzLjUsLTMyNC41IDI0MS41LC0zMjQuNSAyNDEuNSwtMzQzLjUiIHN0cm9rZT0ibm9uZSIvPiYjeGE7PHRleHQgZm9udC1mYW1pbHk9ImFyaWFsIiBmb250LXNpemU9IjEwLjAwIiB0ZXh0LWFuY2hvcj0ic3RhcnQiIHRleHQtZGVjb3JhdGlvbj0idW5kZXJsaW5lIiB4PSIxNzEuNSIgeT0iLTMzMi41Ij5TY2FuSW5mby5GaWVsZDwvdGV4dD4mI3hhOzwvYT4mI3hhOzwvZz4mI3hhOzwvZz4mI3hhOzwhLS0gU2NhbkluZm8mIzQ1OyZndDtTY2FuSW5mby5GaWVsZCAtLT4mI3hhOzxnIGNsYXNzPSJlZGdlIiBpZD0iZWRnZTMxIj48dGl0bGU+U2NhbkluZm8tJmd0O1NjYW5JbmZvLkZpZWxkPC90aXRsZT4mI3hhOzxwYXRoIGQ9Ik0yMDIuNSwtMzgyLjg5QzIwMi41LC0zNzAuMzQ4IDIwMi41LC0zNTMuNjQzIDIwMi41LC0zNDMuNjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gU2Vzc2lvbiYjNDU7Jmd0O1NjYW4gLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UzMiI+PHRpdGxlPlNlc3Npb24tJmd0O1NjYW48L3RpdGxlPiYjeGE7PHBhdGggZD0iTTI2My44OTIsLTUyNC43OTdDMjUxLjE1NywtNTEzLjk0OSAyMzQuNjI2LC00OTkuODY3IDIyMS45MjcsLTQ4OS4wNDkiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMjUwOTgwIiBzdHJva2Utd2lkdGg9IjAuNzUiLz4mI3hhOzwvZz4mI3hhOzwhLS0gU2NhbkluZm8uRmllbGQmIzQ1OyZndDtNb3Rpb25Db3JyZWN0aW9uLlN1bW1hcnkgLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UzMyI+PHRpdGxlPlNjYW5JbmZvLkZpZWxkLSZndDtNb3Rpb25Db3JyZWN0aW9uLlN1bW1hcnk8L3RpdGxlPiYjeGE7PHBhdGggZD0iTTE4OS4wMiwtMzI0LjE5OEMxNzcuNDA1LC0zMTUuNzMyIDE2MS40NDUsLTMwMS43NjggMTU0LjUsLTI4NSAxNDguNTQ3LC0yNzAuNjI5IDE0OC4wNjMsLTI2NC4xNjEgMTU0LjUsLTI1MCAxNjIuNDcxLC0yMzIuNDYzIDE4MC4zNjYsLTIxOC44MjkgMTkzLjU5MSwtMjEwLjY0NiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4yNTA5ODAiIHN0cm9rZS13aWR0aD0iMC43NSIvPiYjeGE7PC9nPiYjeGE7PCEtLSBQcm9jZXNzaW5nTWV0aG9kIC0tPiYjeGE7PGcgY2xhc3M9Im5vZGUiIGlkPSJub2RlMjciPjx0aXRsZT5Qcm9jZXNzaW5nTWV0aG9kPC90aXRsZT4mI3hhOzxnIGlkPSJhX25vZGUyNyI+PGEgeGxpbms6dGl0bGU9InByb2Nlc3NpbmdfbWV0aG9kIMKgwqDCoCYjMTA7LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tJiMxMDtwcm9jZXNzaW5nX21ldGhvZF9kZXNjICYjMTA7Ij4mI3hhOzxwb2x5Z29uIGZpbGw9IiMwMDAwMDAiIGZpbGwtb3BhY2l0eT0iMC4xMjU0OTAiIHBvaW50cz0iNDI5LjUsLTU2MCAzMzEuNSwtNTYwIDMzMS41LC01MjUgNDI5LjUsLTUyNSA0MjkuNSwtNTYwIiBzdHJva2U9Im5vbmUiLz4mI3hhOzx0ZXh0IGZvbnQtZmFtaWx5PSJhcmlhbCIgZm9udC1zaXplPSIxMC4wMCIgdGV4dC1hbmNob3I9InN0YXJ0IiB0ZXh0LWRlY29yYXRpb249InVuZGVybGluZSIgeD0iMzM5LjUiIHk9Ii01NDEiPlByb2Nlc3NpbmdNZXRob2Q8L3RleHQ+JiN4YTs8L2E+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8IS0tIFByb2Nlc3NpbmdNZXRob2QmIzQ1OyZndDtQcm9jZXNzaW5nUGFyYW1TZXQgLS0+JiN4YTs8ZyBjbGFzcz0iZWRnZSIgaWQ9ImVkZ2UzNCI+PHRpdGxlPlByb2Nlc3NpbmdNZXRob2QtJmd0O1Byb2Nlc3NpbmdQYXJhbVNldDwvdGl0bGU+JiN4YTs8cGF0aCBkPSJNMzc2LjE0MywtNTI0Ljc5N0MzNzMuMzEzLC01MTMuOTQ5IDM2OS42MzksLTQ5OS44NjcgMzY2LjgxNywtNDg5LjA0OSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtZGFzaGFycmF5PSI1LDIiIHN0cm9rZS1vcGFjaXR5PSIwLjI1MDk4MCIgc3Ryb2tlLXdpZHRoPSIwLjc1Ii8+JiN4YTs8L2c+JiN4YTs8L2c+JiN4YTs8L3N2Zz4=" preserveAspectRatio="none"/><rect x="549" y="380" width="240" height="120" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 238px; height: 1px; padding-top: 440px; margin-left: 550px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 24px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">IMAGING <br />ELEMENT</div></div></div></foreignObject><text x="669" y="447" fill="#000000" font-family="Helvetica" font-size="24px" text-anchor="middle">IMAGING...</text></switch></g><path d="M 431 200 L 451 120" fill="none" stroke="#666666" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><path d="M 341 180 L 321 80" fill="none" stroke="#666666" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><path d="M 1 50 L 321 80" fill="none" stroke="#666666" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><path d="M 341 186 L 431 206" fill="none" stroke="#666666" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><path d="M 451 120.02 L 790.79 80.15" fill="none" stroke="#666666" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><rect x="471" y="0" width="300" height="120" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 298px; height: 1px; padding-top: 60px; margin-left: 472px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 24px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">UPSTREAM PIPELINE</div></div></div></foreignObject><text x="621" y="67" fill="#000000" font-family="Helvetica" font-size="24px" text-anchor="middle">UPSTREAM PIPELINE</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg> \ No newline at end of file +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1125pt" + height="719pt" + viewBox="0.00 0.00 1124.81 719.00" + version="1.1" + id="svg1688" + sodipodi:docname="attached_miniscope_element.svg" + inkscape:version="1.0.1 (c497b03c, 2020-09-10)"> + <metadata + id="metadata1694"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs1692" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1720" + inkscape:window-height="1323" + id="namedview1690" + showgrid="false" + inkscape:zoom="0.73066667" + inkscape:cx="750" + inkscape:cy="479.33333" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="0" + inkscape:current-layer="svg1688" + inkscape:document-rotation="0" /> + <g + id="graph0" + class="graph" + transform="scale(1 1) rotate(0) translate(4 715)"> + <title + id="title1162">%3</title> + <polygon + fill="white" + stroke="transparent" + points="-4,4 -4,-715 1120.81,-715 1120.81,4 -4,4" + id="polygon1164" /> + <!-- 30 --> + <g + id="node1" + class="node"> + <title + id="title1166">30</title> + <ellipse + fill="#ff8800" + fill-opacity="0.501961" + stroke="#ff8800" + stroke-opacity="0.501961" + cx="516.5" + cy="-205.5" + rx="2" + ry="2" + id="ellipse1168" /> + <text + text-anchor="middle" + x="516.5" + y="-205.2" + font-family="arial" + font-size="1.00" + fill="#ff8800" + fill-opacity="0.501961" + id="text1170">30</text> + </g> + <!-- MotionCorrection --> + <g + id="node16" + class="node"> + <title + id="title1173">MotionCorrection</title> + <g + id="a_node16"> + <a + xlink:title="→ Curation ------------------------------ → Channel.proj(motion_correct_channel="channel") " + id="a1179"> + <ellipse + fill="#00007f" + fill-opacity="0.250980" + stroke="#00007f" + stroke-opacity="0.250980" + cx="313.5" + cy="-134.5" + rx="67.5" + ry="17.5" + id="ellipse1175" /> + <text + text-anchor="middle" + x="313.5" + y="-131.4" + font-family="arial" + font-size="12.00" + fill="#00007f" + fill-opacity="0.627451" + id="text1177">MotionCorrection</text> + </a> + </g> + </g> + <!-- 30->MotionCorrection --> + <g + id="edge1" + class="edge"> + <title + id="title1183">30->MotionCorrection</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M514.75,-203.9C500.83,-199.17 407.49,-167.45 352.95,-148.91" + id="path1185" /> + </g> + <!-- 31 --> + <g + id="node2" + class="node"> + <title + id="title1188">31</title> + <ellipse + fill="#ff8800" + fill-opacity="0.501961" + stroke="#ff8800" + stroke-opacity="0.501961" + cx="1045.5" + cy="-205.5" + rx="2" + ry="2" + id="ellipse1190" /> + <text + text-anchor="middle" + x="1045.5" + y="-205.2" + font-family="arial" + font-size="1.00" + fill="#ff8800" + fill-opacity="0.501961" + id="text1192">31</text> + </g> + <!-- Segmentation.Mask --> + <g + id="node17" + class="node"> + <title + id="title1195">Segmentation.Mask</title> + <g + id="a_node17"> + <a + xlink:title="→ Segmentation mask_id ------------------------------ → Channel.proj(segmentation_channel="channel") mask_npix mask_center_x=null mask_center_y=null mask_xpix=null mask_ypix=null mask_weights " + id="a1201"> + <polygon + fill="transparent" + stroke="transparent" + points="1097.5,-144 993.5,-144 993.5,-125 1097.5,-125 1097.5,-144" + id="polygon1197" /> + <text + text-anchor="start" + x="1001.5" + y="-133" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1199">Segmentation.Mask</text> + </a> + </g> + </g> + <!-- 31->Segmentation.Mask --> + <g + id="edge2" + class="edge"> + <title + id="title1205">31->Segmentation.Mask</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M1045.5,-203.3C1045.5,-196.32 1045.5,-160.64 1045.5,-144.03" + id="path1207" /> + </g> + <!-- 32 --> + <g + id="node3" + class="node"> + <title + id="title1210">32</title> + <ellipse + fill="#ff8800" + fill-opacity="0.501961" + stroke="#ff8800" + stroke-opacity="0.501961" + cx="1103.5" + cy="-205.5" + rx="2" + ry="2" + id="ellipse1212" /> + <text + text-anchor="middle" + x="1103.5" + y="-205.2" + font-family="arial" + font-size="1.00" + fill="#ff8800" + fill-opacity="0.501961" + id="text1214">32</text> + </g> + <!-- Fluorescence.Trace --> + <g + id="node22" + class="node"> + <title + id="title1217">Fluorescence.Trace</title> + <g + id="a_node22"> + <a + xlink:title="→ Fluorescence → Segmentation.Mask → Channel.proj(fluorescence_channel="channel") ------------------------------ fluorescence neuropil_fluorescence=null " + id="a1223"> + <polygon + fill="transparent" + stroke="transparent" + points="982,-77.5 879,-77.5 879,-58.5 982,-58.5 982,-77.5" + id="polygon1219" /> + <text + text-anchor="middle" + x="930.5" + y="-65.5" + font-family="arial" + font-size="10.00" + id="text1221">Fluorescence.Trace</text> + </a> + </g> + </g> + <!-- 32->Fluorescence.Trace --> + <g + id="edge3" + class="edge"> + <title + id="title1227">32->Fluorescence.Trace</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M1103.95,-203.5C1107.35,-195.82 1128.27,-145.51 1106.5,-117 1091.41,-97.24 1028.63,-83.66 982.33,-76.12" + id="path1229" /> + </g> + <!-- Subject --> + <g + id="node4" + class="node"> + <title + id="title1232">Subject</title> + <g + id="a_node4"> + <a + xlink:title="subject ------------------------------ sex subject_birth_date subject_description="" " + id="a1238"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="467,-711 410,-711 410,-676 467,-676 467,-711" + id="polygon1234" /> + <text + text-anchor="start" + x="418" + y="-691.4" + font-family="arial" + text-decoration="underline" + font-size="12.00" + fill="darkgreen" + id="text1236">Subject</text> + </a> + </g> + </g> + <!-- Session --> + <g + id="node5" + class="node"> + <title + id="title1242">Session</title> + <g + id="a_node5"> + <a + xlink:title="→ Subject session_datetime " + id="a1248"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="468.5,-640 408.5,-640 408.5,-605 468.5,-605 468.5,-640" + id="polygon1244" /> + <text + text-anchor="start" + x="416.5" + y="-620.4" + font-family="arial" + text-decoration="underline" + font-size="12.00" + fill="darkgreen" + id="text1246">Session</text> + </a> + </g> + </g> + <!-- Subject->Session --> + <g + id="edge4" + class="edge"> + <title + id="title1252">Subject->Session</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M438.5,-675.8C438.5,-664.95 438.5,-650.87 438.5,-640.05" + id="path1254" /> + </g> + <!-- Recording --> + <g + id="node7" + class="node"> + <title + id="title1257">Recording</title> + <g + id="a_node7"> + <a + xlink:title="→ Session recording_id ------------------------------ → Equipment → AcquisitionSoftware recording_directory recording_notes="" " + id="a1263"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="574.5,-569 502.5,-569 502.5,-534 574.5,-534 574.5,-569" + id="polygon1259" /> + <text + text-anchor="start" + x="510.5" + y="-549.4" + font-family="arial" + text-decoration="underline" + font-size="12.00" + fill="darkgreen" + id="text1261">Recording</text> + </a> + </g> + </g> + <!-- Session->Recording --> + <g + id="edge5" + class="edge"> + <title + id="title1267">Session->Recording</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M462.45,-604.97C478.17,-594.13 498.65,-580 514.4,-569.13" + id="path1269" /> + </g> + <!-- RecordingLocation --> + <g + id="node6" + class="node"> + <title + id="title1272">RecordingLocation</title> + <g + id="a_node6"> + <a + xlink:title="→ Recording ------------------------------ → AnatomicalLocation " + id="a1278"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="472.5,-498 354.5,-498 354.5,-463 472.5,-463 472.5,-498" + id="polygon1274" /> + <text + text-anchor="middle" + x="413.5" + y="-477.4" + font-family="arial" + font-size="12.00" + fill="darkgreen" + id="text1276">RecordingLocation</text> + </a> + </g> + </g> + <!-- Recording->RecordingLocation --> + <g + id="edge6" + class="edge"> + <title + id="title1282">Recording->RecordingLocation</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M508.56,-533.97C488.91,-523.13 463.31,-509 443.63,-498.13" + id="path1284" /> + </g> + <!-- RecordingInfo --> + <g + id="node11" + class="node"> + <title + id="title1287">RecordingInfo</title> + <g + id="a_node11"> + <a + xlink:title="→ Recording ------------------------------ nchannels nframes px_height=null px_width=null um_height=null um_width=null fps gain=null spatial_downsample=1 led_power time_stamps " + id="a1293"> + <ellipse + fill="#00007f" + fill-opacity="0.250980" + stroke="#00007f" + stroke-opacity="0.250980" + cx="548.5" + cy="-480.5" + rx="57.5" + ry="17.5" + id="ellipse1289" /> + <text + text-anchor="middle" + x="548.5" + y="-477.4" + font-family="arial" + font-size="12.00" + fill="#00007f" + fill-opacity="0.627451" + id="text1291">RecordingInfo</text> + </a> + </g> + </g> + <!-- Recording->RecordingInfo --> + <g + id="edge7" + class="edge"> + <title + id="title1297">Recording->RecordingInfo</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M540.92,-533.8C542.49,-522.95 544.53,-508.87 546.1,-498.05" + id="path1299" /> + </g> + <!-- ProcessingTask --> + <g + id="node8" + class="node"> + <title + id="title1302">ProcessingTask</title> + <g + id="a_node8"> + <a + xlink:title="→ RecordingInfo → ProcessingParamSet ------------------------------ processing_output_dir task_mode="load" " + id="a1308"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="722,-427 621,-427 621,-392 722,-392 722,-427" + id="polygon1304" /> + <text + text-anchor="middle" + x="671.5" + y="-406.4" + font-family="arial" + font-size="12.00" + fill="darkgreen" + id="text1306">ProcessingTask</text> + </a> + </g> + </g> + <!-- Processing --> + <g + id="node19" + class="node"> + <title + id="title1312">Processing</title> + <g + id="a_node19"> + <a + xlink:title="→ ProcessingTask ------------------------------ processing_time package_version="" " + id="a1318"> + <ellipse + fill="#ff0000" + fill-opacity="0.125490" + stroke="#ff0000" + stroke-opacity="0.125490" + cx="671.5" + cy="-343" + rx="13" + ry="13" + id="ellipse1314" /> + <text + text-anchor="middle" + x="671.5" + y="-339.9" + font-family="arial" + font-size="12.00" + fill="#7f0000" + fill-opacity="0.627451" + id="text1316">Processing</text> + </a> + </g> + </g> + <!-- ProcessingTask->Processing --> + <g + id="edge8" + class="edge"> + <title + id="title1322">ProcessingTask->Processing</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M671.5,-391.89C671.5,-380.69 671.5,-366.17 671.5,-356.07" + id="path1324" /> + </g> + <!-- Curation --> + <g + id="node9" + class="node"> + <title + id="title1327">Curation</title> + <g + id="a_node9"> + <a + xlink:title="→ Processing curation_id ------------------------------ curation_time curation_output_dir manual_curation curation_note="" " + id="a1333"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="702.5,-294 640.5,-294 640.5,-259 702.5,-259 702.5,-294" + id="polygon1329" /> + <text + text-anchor="start" + x="648.5" + y="-274.4" + font-family="arial" + text-decoration="underline" + font-size="12.00" + fill="darkgreen" + id="text1331">Curation</text> + </a> + </g> + </g> + <!-- Curation->MotionCorrection --> + <g + id="edge9" + class="edge"> + <title + id="title1337">Curation->MotionCorrection</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M640.41,-267.55C605.42,-258.26 547.11,-241.75 498.5,-223 441.49,-201 377.57,-169.07 341.77,-150.48" + id="path1339" /> + </g> + <!-- Segmentation --> + <g + id="node18" + class="node"> + <title + id="title1342">Segmentation</title> + <g + id="a_node18"> + <a + xlink:title="→ Curation " + id="a1348"> + <ellipse + fill="#ff0000" + fill-opacity="0.125490" + stroke="#ff0000" + stroke-opacity="0.125490" + cx="951.5" + cy="-205.5" + rx="13" + ry="13" + id="ellipse1344" /> + <text + text-anchor="middle" + x="951.5" + y="-202.4" + font-family="arial" + font-size="12.00" + fill="#7f0000" + fill-opacity="0.627451" + id="text1346">Segmentation</text> + </a> + </g> + </g> + <!-- Curation->Segmentation --> + <g + id="edge10" + class="edge"> + <title + id="title1352">Curation->Segmentation</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M702.53,-267.85C763.76,-252.76 897.75,-219.75 939.1,-209.56" + id="path1354" /> + </g> + <!-- RecordingInfo.File --> + <g + id="node10" + class="node"> + <title + id="title1357">RecordingInfo.File</title> + <g + id="a_node10"> + <a + xlink:title="→ RecordingInfo recording_file_id ------------------------------ recording_file_path " + id="a1363"> + <polygon + fill="transparent" + stroke="transparent" + points="596.5,-419 500.5,-419 500.5,-400 596.5,-400 596.5,-419" + id="polygon1359" /> + <text + text-anchor="start" + x="508.5" + y="-408" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1361">RecordingInfo.File</text> + </a> + </g> + </g> + <!-- RecordingInfo->ProcessingTask --> + <g + id="edge11" + class="edge"> + <title + id="title1367">RecordingInfo->ProcessingTask</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M574.59,-464.87C594.33,-453.79 621.46,-438.57 642,-427.05" + id="path1369" /> + </g> + <!-- RecordingInfo->RecordingInfo.File --> + <g + id="edge12" + class="edge"> + <title + id="title1372">RecordingInfo->RecordingInfo.File</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M548.5,-462.8C548.5,-449.08 548.5,-430.19 548.5,-419.27" + id="path1374" /> + </g> + <!-- MotionCorrection.Summary --> + <g + id="node12" + class="node"> + <title + id="title1377">MotionCorrection.Summary</title> + <g + id="a_node12"> + <a + xlink:title="→ MotionCorrection ------------------------------ ref_image=null average_image correlation_image=null max_proj_image=null " + id="a1383"> + <polygon + fill="transparent" + stroke="transparent" + points="572.5,-77.5 436.5,-77.5 436.5,-58.5 572.5,-58.5 572.5,-77.5" + id="polygon1379" /> + <text + text-anchor="middle" + x="504.5" + y="-65.5" + font-family="arial" + font-size="10.00" + id="text1381">MotionCorrection.Summary</text> + </a> + </g> + </g> + <!-- MotionCorrection.RigidMotionCorrection --> + <g + id="node13" + class="node"> + <title + id="title1387">MotionCorrection.RigidMotionCorrection</title> + <g + id="a_node13"> + <a + xlink:title="→ MotionCorrection ------------------------------ outlier_frames=null y_shifts x_shifts y_std x_std " + id="a1393"> + <polygon + fill="transparent" + stroke="transparent" + points="191,-77.5 0,-77.5 0,-58.5 191,-58.5 191,-77.5" + id="polygon1389" /> + <text + text-anchor="middle" + x="95.5" + y="-65.5" + font-family="arial" + font-size="10.00" + id="text1391">MotionCorrection.RigidMotionCorrection</text> + </a> + </g> + </g> + <!-- MotionCorrection.NonRigidMotionCorrection --> + <g + id="node14" + class="node"> + <title + id="title1397">MotionCorrection.NonRigidMotionCorrection</title> + <g + id="a_node14"> + <a + xlink:title="→ MotionCorrection ------------------------------ outlier_frames=null block_height block_width block_count_y block_count_x " + id="a1403"> + <polygon + fill="transparent" + stroke="transparent" + points="418,-77.5 209,-77.5 209,-58.5 418,-58.5 418,-77.5" + id="polygon1399" /> + <text + text-anchor="middle" + x="313.5" + y="-65.5" + font-family="arial" + font-size="10.00" + id="text1401">MotionCorrection.NonRigidMotionCorrection</text> + </a> + </g> + </g> + <!-- MotionCorrection.Block --> + <g + id="node15" + class="node"> + <title + id="title1407">MotionCorrection.Block</title> + <g + id="a_node15"> + <a + xlink:title="→ MotionCorrection.NonRigidMotionCorrection block_id ------------------------------ block_y block_x y_shifts x_shifts y_std x_std " + id="a1413"> + <polygon + fill="transparent" + stroke="transparent" + points="373,-19 254,-19 254,0 373,0 373,-19" + id="polygon1409" /> + <text + text-anchor="start" + x="262" + y="-8" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1411">MotionCorrection.Block</text> + </a> + </g> + </g> + <!-- MotionCorrection.NonRigidMotionCorrection->MotionCorrection.Block --> + <g + id="edge13" + class="edge"> + <title + id="title1417">MotionCorrection.NonRigidMotionCorrection->MotionCorrection.Block</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M313.5,-58.17C313.5,-47.5 313.5,-29.65 313.5,-19.08" + id="path1419" /> + </g> + <!-- MotionCorrection->MotionCorrection.Summary --> + <g + id="edge14" + class="edge"> + <title + id="title1422">MotionCorrection->MotionCorrection.Summary</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M353.08,-120.14C390.99,-107.33 447.07,-88.4 479.27,-77.52" + id="path1424" /> + </g> + <!-- MotionCorrection->MotionCorrection.RigidMotionCorrection --> + <g + id="edge15" + class="edge"> + <title + id="title1427">MotionCorrection->MotionCorrection.RigidMotionCorrection</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M270.95,-120.91C227.71,-108.12 161.9,-88.64 124.38,-77.54" + id="path1429" /> + </g> + <!-- MotionCorrection->MotionCorrection.NonRigidMotionCorrection --> + <g + id="edge16" + class="edge"> + <title + id="title1432">MotionCorrection->MotionCorrection.NonRigidMotionCorrection</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M313.5,-116.89C313.5,-104.35 313.5,-87.64 313.5,-77.64" + id="path1434" /> + </g> + <!-- MaskClassification.MaskType --> + <g + id="node20" + class="node"> + <title + id="title1437">MaskClassification.MaskType</title> + <g + id="a_node20"> + <a + xlink:title="→ MaskClassification → Segmentation.Mask ------------------------------ → MaskType confidence=null " + id="a1443"> + <polygon + fill="transparent" + stroke="transparent" + points="738.5,-77.5 590.5,-77.5 590.5,-58.5 738.5,-58.5 738.5,-77.5" + id="polygon1439" /> + <text + text-anchor="middle" + x="664.5" + y="-65.5" + font-family="arial" + font-size="10.00" + id="text1441">MaskClassification.MaskType</text> + </a> + </g> + </g> + <!-- Segmentation.Mask->MaskClassification.MaskType --> + <g + id="edge17" + class="edge"> + <title + id="title1447">Segmentation.Mask->MaskClassification.MaskType</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M1016.5,-124.92C1006.43,-122.14 995.02,-119.2 984.5,-117 900.51,-99.4 802.82,-85.72 737.69,-77.54" + id="path1449" /> + </g> + <!-- Segmentation.Mask->Fluorescence.Trace --> + <g + id="edge18" + class="edge"> + <title + id="title1452">Segmentation.Mask->Fluorescence.Trace</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M1029.99,-124.8C1007.92,-112.42 967.7,-89.86 945.76,-77.56" + id="path1454" /> + </g> + <!-- Segmentation->Segmentation.Mask --> + <g + id="edge19" + class="edge"> + <title + id="title1457">Segmentation->Segmentation.Mask</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M961.56,-197.12C979.08,-184.25 1015.12,-157.8 1033.76,-144.12" + id="path1459" /> + </g> + <!-- MaskClassification --> + <g + id="node21" + class="node"> + <title + id="title1462">MaskClassification</title> + <g + id="a_node21"> + <a + xlink:title="→ Segmentation → MaskClassificationMethod " + id="a1468"> + <ellipse + fill="#ff0000" + fill-opacity="0.125490" + stroke="#ff0000" + stroke-opacity="0.125490" + cx="664.5" + cy="-134.5" + rx="13" + ry="13" + id="ellipse1464" /> + <text + text-anchor="middle" + x="664.5" + y="-131.4" + font-family="arial" + font-size="12.00" + fill="#7f0000" + fill-opacity="0.627451" + id="text1466">MaskClassification</text> + </a> + </g> + </g> + <!-- Segmentation->MaskClassification --> + <g + id="edge20" + class="edge"> + <title + id="title1472">Segmentation->MaskClassification</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M938.66,-201.44C904.89,-193.37 810.8,-170.88 732.5,-152 713.13,-147.33 690.67,-141.87 677.03,-138.55" + id="path1474" /> + </g> + <!-- Fluorescence --> + <g + id="node23" + class="node"> + <title + id="title1477">Fluorescence</title> + <g + id="a_node23"> + <a + xlink:title="→ Segmentation " + id="a1483"> + <ellipse + fill="#ff0000" + fill-opacity="0.125490" + stroke="#ff0000" + stroke-opacity="0.125490" + cx="930.5" + cy="-134.5" + rx="13" + ry="13" + id="ellipse1479" /> + <text + text-anchor="middle" + x="930.5" + y="-131.4" + font-family="arial" + font-size="12.00" + fill="#7f0000" + fill-opacity="0.627451" + id="text1481">Fluorescence</text> + </a> + </g> + </g> + <!-- Segmentation->Fluorescence --> + <g + id="edge21" + class="edge"> + <title + id="title1487">Segmentation->Fluorescence</title> + <path + fill="none" + stroke="#000000" + stroke-width="2" + stroke-opacity="0.250980" + d="M947.94,-192.8C944.05,-180.03 937.87,-159.73 934.01,-147.03" + id="path1489" /> + </g> + <!-- Processing->Curation --> + <g + id="edge22" + class="edge"> + <title + id="title1492">Processing->Curation</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M671.5,-329.87C671.5,-319.8 671.5,-305.37 671.5,-294.2" + id="path1494" /> + </g> + <!-- MaskClassification->MaskClassification.MaskType --> + <g + id="edge23" + class="edge"> + <title + id="title1497">MaskClassification->MaskClassification.MaskType</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M664.5,-121.37C664.5,-108.67 664.5,-89.04 664.5,-77.77" + id="path1499" /> + </g> + <!-- Activity.Trace --> + <g + id="node24" + class="node"> + <title + id="title1502">Activity.Trace</title> + <g + id="a_node24"> + <a + xlink:title="→ Activity → Fluorescence.Trace ------------------------------ activity_trace " + id="a1508"> + <polygon + fill="transparent" + stroke="transparent" + points="872,-19 795,-19 795,0 872,0 872,-19" + id="polygon1504" /> + <text + text-anchor="middle" + x="833.5" + y="-7" + font-family="arial" + font-size="10.00" + id="text1506">Activity.Trace</text> + </a> + </g> + </g> + <!-- Fluorescence.Trace->Activity.Trace --> + <g + id="edge24" + class="edge"> + <title + id="title1512">Fluorescence.Trace->Activity.Trace</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M915.77,-58.42C897.54,-47.8 866.67,-29.82 848.37,-19.16" + id="path1514" /> + </g> + <!-- Fluorescence->Fluorescence.Trace --> + <g + id="edge25" + class="edge"> + <title + id="title1517">Fluorescence->Fluorescence.Trace</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M930.5,-121.37C930.5,-108.67 930.5,-89.04 930.5,-77.77" + id="path1519" /> + </g> + <!-- Activity --> + <g + id="node25" + class="node"> + <title + id="title1522">Activity</title> + <g + id="a_node25"> + <a + xlink:title="→ Fluorescence → ActivityExtractionMethod " + id="a1528"> + <ellipse + fill="#ff0000" + fill-opacity="0.125490" + stroke="#ff0000" + stroke-opacity="0.125490" + cx="833.5" + cy="-68" + rx="13" + ry="13" + id="ellipse1524" /> + <text + text-anchor="middle" + x="833.5" + y="-64.9" + font-family="arial" + font-size="12.00" + fill="#7f0000" + fill-opacity="0.627451" + id="text1526">Activity</text> + </a> + </g> + </g> + <!-- Fluorescence->Activity --> + <g + id="edge26" + class="edge"> + <title + id="title1532">Fluorescence->Activity</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M920.12,-126.6C901.55,-114.25 862.83,-88.5 844.1,-76.05" + id="path1534" /> + </g> + <!-- Activity->Activity.Trace --> + <g + id="edge27" + class="edge"> + <title + id="title1537">Activity->Activity.Trace</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M833.5,-54.74C833.5,-43.97 833.5,-28.56 833.5,-19.06" + id="path1539" /> + </g> + <!-- ProcessingParamSet --> + <g + id="node26" + class="node"> + <title + id="title1542">ProcessingParamSet</title> + <g + id="a_node26"> + <a + xlink:title="paramset_id ------------------------------ → ProcessingMethod paramset_desc param_set_hash params UNIQUE INDEX (param_set_hash) " + id="a1548"> + <polygon + fill="#000000" + fill-opacity="0.125490" + stroke="transparent" + points="733,-498 624,-498 624,-463 733,-463 733,-498" + id="polygon1544" /> + <text + text-anchor="start" + x="632" + y="-479" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1546">ProcessingParamSet</text> + </a> + </g> + </g> + <!-- ProcessingParamSet->ProcessingTask --> + <g + id="edge28" + class="edge"> + <title + id="title1552">ProcessingParamSet->ProcessingTask</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M676.81,-462.8C675.7,-451.95 674.28,-437.87 673.18,-427.05" + id="path1554" /> + </g> + <!-- ProcessingMethod --> + <g + id="node27" + class="node"> + <title + id="title1557">ProcessingMethod</title> + <g + id="a_node27"> + <a + xlink:title="processing_method ------------------------------ processing_method_desc " + id="a1563"> + <polygon + fill="#000000" + fill-opacity="0.125490" + stroke="transparent" + points="727.5,-569 629.5,-569 629.5,-534 727.5,-534 727.5,-569" + id="polygon1559" /> + <text + text-anchor="start" + x="637.5" + y="-550" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1561">ProcessingMethod</text> + </a> + </g> + </g> + <!-- ProcessingMethod->ProcessingParamSet --> + <g + id="edge29" + class="edge"> + <title + id="title1567">ProcessingMethod->ProcessingParamSet</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M678.5,-533.8C678.5,-522.95 678.5,-508.87 678.5,-498.05" + id="path1569" /> + </g> + <!-- MaskType --> + <g + id="node28" + class="node"> + <title + id="title1572">MaskType</title> + <g + id="a_node28"> + <a + xlink:title="mask_type " + id="a1578"> + <polygon + fill="#000000" + fill-opacity="0.125490" + stroke="transparent" + points="587.5,-152 525.5,-152 525.5,-117 587.5,-117 587.5,-152" + id="polygon1574" /> + <text + text-anchor="start" + x="533.5" + y="-133" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1576">MaskType</text> + </a> + </g> + </g> + <!-- MaskType->MaskClassification.MaskType --> + <g + id="edge30" + class="edge"> + <title + id="title1582">MaskType->MaskClassification.MaskType</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M584.31,-116.89C605.31,-104.35 633.28,-87.64 650.03,-77.64" + id="path1584" /> + </g> + <!-- MaskClassificationMethod --> + <g + id="node29" + class="node"> + <title + id="title1587">MaskClassificationMethod</title> + <g + id="a_node29"> + <a + xlink:title="mask_classification_method " + id="a1593"> + <polygon + fill="#000000" + fill-opacity="0.125490" + stroke="transparent" + points="730.5,-223 598.5,-223 598.5,-188 730.5,-188 730.5,-223" + id="polygon1589" /> + <text + text-anchor="start" + x="606.5" + y="-204" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1591">MaskClassificationMethod</text> + </a> + </g> + </g> + <!-- MaskClassificationMethod->MaskClassification --> + <g + id="edge31" + class="edge"> + <title + id="title1597">MaskClassificationMethod->MaskClassification</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M664.5,-187.8C664.5,-175.35 664.5,-158.66 664.5,-147.51" + id="path1599" /> + </g> + <!-- Channel --> + <g + id="node30" + class="node"> + <title + id="title1602">Channel</title> + <g + id="a_node30"> + <a + xlink:title="channel " + id="a1608"> + <polygon + fill="#000000" + fill-opacity="0.125490" + stroke="transparent" + points="1072,-294 1019,-294 1019,-259 1072,-259 1072,-294" + id="polygon1604" /> + <text + text-anchor="start" + x="1027" + y="-275" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1606">Channel</text> + </a> + </g> + </g> + <!-- Channel->30 --> + <g + id="edge32" + class="edge"> + <title + id="title1612">Channel->30</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M1018.89,-273.44C948.46,-267.79 751.53,-250.56 589.5,-223 560.81,-218.12 526.44,-209.15 518.29,-206.98" + id="path1614" /> + </g> + <!-- Channel->31 --> + <g + id="edge33" + class="edge"> + <title + id="title1617">Channel->31</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M1045.5,-258.8C1045.5,-240.93 1045.5,-214.29 1045.5,-207.91" + id="path1619" /> + </g> + <!-- Channel->32 --> + <g + id="edge34" + class="edge"> + <title + id="title1622">Channel->32</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M1059.54,-258.8C1074.56,-240.93 1096.95,-214.29 1102.32,-207.91" + id="path1624" /> + </g> + <!-- ActivityExtractionMethod --> + <g + id="node31" + class="node"> + <title + id="title1627">ActivityExtractionMethod</title> + <g + id="a_node31"> + <a + xlink:title="extraction_method " + id="a1633"> + <polygon + fill="#000000" + fill-opacity="0.125490" + stroke="transparent" + points="867.5,-152 741.5,-152 741.5,-117 867.5,-117 867.5,-152" + id="polygon1629" /> + <text + text-anchor="start" + x="749.5" + y="-133" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1631">ActivityExtractionMethod</text> + </a> + </g> + </g> + <!-- ActivityExtractionMethod->Activity --> + <g + id="edge35" + class="edge"> + <title + id="title1637">ActivityExtractionMethod->Activity</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-opacity="0.250980" + d="M811.97,-116.89C817.14,-105.39 823.88,-90.4 828.43,-80.27" + id="path1639" /> + </g> + <!-- AcquisitionSoftware --> + <g + id="node32" + class="node"> + <title + id="title1642">AcquisitionSoftware</title> + <g + id="a_node32"> + <a + xlink:title="acquisition_software " + id="a1648"> + <polygon + fill="#000000" + fill-opacity="0.125490" + stroke="transparent" + points="590.5,-640 486.5,-640 486.5,-605 590.5,-605 590.5,-640" + id="polygon1644" /> + <text + text-anchor="start" + x="494.5" + y="-621" + font-family="arial" + text-decoration="underline" + font-size="10.00" + id="text1646">AcquisitionSoftware</text> + </a> + </g> + </g> + <!-- AcquisitionSoftware->Recording --> + <g + id="edge36" + class="edge"> + <title + id="title1652">AcquisitionSoftware->Recording</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M538.5,-604.8C538.5,-593.95 538.5,-579.87 538.5,-569.05" + id="path1654" /> + </g> + <!-- Equipment --> + <g + id="node33" + class="node"> + <title + id="title1657">Equipment</title> + <g + id="a_node33"> + <a + xlink:title="acquisition_hardware " + id="a1663"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="682.5,-640 608.5,-640 608.5,-605 682.5,-605 682.5,-640" + id="polygon1659" /> + <text + text-anchor="start" + x="616.5" + y="-620.4" + font-family="arial" + text-decoration="underline" + font-size="12.00" + fill="darkgreen" + id="text1661">Equipment</text> + </a> + </g> + </g> + <!-- Equipment->Recording --> + <g + id="edge37" + class="edge"> + <title + id="title1667">Equipment->Recording</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M619.87,-604.97C603.05,-594.13 581.14,-580 564.29,-569.13" + id="path1669" /> + </g> + <!-- AnatomicalLocation --> + <g + id="node34" + class="node"> + <title + id="title1672">AnatomicalLocation</title> + <g + id="a_node34"> + <a + xlink:title="recording_location_id ------------------------------ anatomical_description " + id="a1678"> + <polygon + fill="#00ff00" + fill-opacity="0.188235" + stroke="#00ff00" + stroke-opacity="0.188235" + points="474.5,-569 352.5,-569 352.5,-534 474.5,-534 474.5,-569" + id="polygon1674" /> + <text + text-anchor="start" + x="360.5" + y="-549.4" + font-family="arial" + text-decoration="underline" + font-size="12.00" + fill="darkgreen" + id="text1676">AnatomicalLocation</text> + </a> + </g> + </g> + <!-- AnatomicalLocation->RecordingLocation --> + <g + id="edge38" + class="edge"> + <title + id="title1682">AnatomicalLocation->RecordingLocation</title> + <path + fill="none" + stroke="#000000" + stroke-width="0.75" + stroke-dasharray="5,2" + stroke-opacity="0.250980" + d="M413.5,-533.8C413.5,-522.95 413.5,-508.87 413.5,-498.05" + id="path1684" /> + </g> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="202.23869" + y="51.211006" + id="text1385-8"><tspan + sodipodi:role="line" + x="202.23869" + y="51.211006" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.75" + id="tspan1387-1">Upstream</tspan><tspan + sodipodi:role="line" + x="202.23869" + y="101.21101" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.75" + id="tspan1412">pipeline</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:2.83465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:8.50395, 2.83465;stroke-dashoffset:0;stroke-opacity:1" + d="m 151.68235,126.00844 333.48314,-2.97753 -1.48877,-135.477539 v 0" + id="path1369-8" + sodipodi:nodetypes="cccc" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="848.4212" + y="225.47054" + id="text1385"><tspan + sodipodi:role="line" + id="tspan1383" + x="848.4212" + y="225.47054" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.75">Miniscope</tspan><tspan + sodipodi:role="line" + x="848.4212" + y="275.47052" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.75" + id="tspan1387">Element</tspan></text> +</svg> diff --git a/images/elements_miniscope_diagram.svg b/images/elements_miniscope_diagram.svg deleted file mode 100644 index dcd477d..0000000 --- a/images/elements_miniscope_diagram.svg +++ /dev/null @@ -1,357 +0,0 @@ -<svg height="639pt" viewBox="0.00 0.00 613.50 639.00" width="614pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> -<g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 635)"> -<title>%3</title> -<polygon fill="white" points="-4,4 -4,-635 609.5,-635 609.5,4 -4,4" stroke="none"/> -<!-- 1 --> -<g class="node" id="node1"><title>1</title> -<ellipse cx="98.5" cy="-267.5" fill="#ff8800" fill-opacity="0.501961" rx="2" ry="2" stroke="#ff8800" stroke-opacity="0.501961"/> -<text fill="#ff8800" font-family="arial" font-size="1.00" text-anchor="middle" x="98.5" y="-267.2">1</text> -</g> -<!-- Segmentation.Mask --> -<g class="node" id="node22"><title>Segmentation.Mask</title> -<g id="a_node22"><a xlink:title="→ Segmentation mask ------------------------------ → Channel.proj(seg_channel="channel") mask_npix mask_center_x mask_center_y mask_center_z mask_xpix mask_ypix mask_zpix mask_weights "> -<polygon fill="none" points="391.5,-144 287.5,-144 287.5,-125 391.5,-125 391.5,-144" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="295.5" y="-133">Segmentation.Mask</text> -</a> -</g> -</g> -<!-- 1->Segmentation.Mask --> -<g class="edge" id="edge1"><title>1->Segmentation.Mask</title> -<path d="M98.6472,-265.147C99.6462,-256.647 106.207,-210.269 132.5,-188 183.17,-145.086 213.192,-168.539 277.5,-152 287.317,-149.475 297.988,-146.661 307.667,-144.081" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- 0 --> -<g class="node" id="node2"><title>0</title> -<ellipse cx="66.5" cy="-201" fill="#ff8800" fill-opacity="0.501961" rx="2" ry="2" stroke="#ff8800" stroke-opacity="0.501961"/> -<text fill="#ff8800" font-family="arial" font-size="1.00" text-anchor="middle" x="66.5" y="-200.7">0</text> -</g> -<!-- Fluorescence.Trace --> -<g class="node" id="node19"><title>Fluorescence.Trace</title> -<g id="a_node19"><a xlink:title="→ Fluorescence → Segmentation.Mask → Channel.proj(fluo_channel="channel") ------------------------------ fluorescence neuropil_fluorescence=null "> -<polygon fill="none" points="276.5,-77.5 174.5,-77.5 174.5,-58.5 276.5,-58.5 276.5,-77.5" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="middle" x="225.5" y="-65.5">Fluorescence.Trace</text> -</a> -</g> -</g> -<!-- 0->Fluorescence.Trace --> -<g class="edge" id="edge2"><title>0->Fluorescence.Trace</title> -<path d="M67.8172,-199.137C74.7958,-194.55 108.088,-172.459 133.5,-152 135.636,-150.28 192.789,-98.5954 215.919,-77.6696" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- 2 --> -<g class="node" id="node3"><title>2</title> -<ellipse cx="136.5" cy="-334" fill="#ff8800" fill-opacity="0.501961" rx="2" ry="2" stroke="#ff8800" stroke-opacity="0.501961"/> -<text fill="#ff8800" font-family="arial" font-size="1.00" text-anchor="middle" x="136.5" y="-333.7">2</text> -</g> -<!-- MotionCorrection --> -<g class="node" id="node4"><title>MotionCorrection</title> -<g id="a_node4"><a xlink:title="→ Processing ------------------------------ → Channel.proj(mc_channel="channel") "> -<ellipse cx="230.5" cy="-267.5" fill="#00007f" fill-opacity="0.250980" rx="66.7502" ry="17.5" stroke="#00007f" stroke-opacity="0.250980"/> -<text fill="#00007f" font-family="arial" font-size="12.00" text-anchor="middle" x="230.5" y="-264.4">MotionCorrection</text> -</a> -</g> -</g> -<!-- 2->MotionCorrection --> -<g class="edge" id="edge3"><title>2->MotionCorrection</title> -<path d="M137.556,-332.276C144.333,-327.625 182.243,-301.613 207.505,-284.278" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- MotionCorrection.Summary --> -<g class="node" id="node9"><title>MotionCorrection.Summary</title> -<g id="a_node9"><a xlink:title="→ MotionCorrection → ScanInfo.Field ------------------------------ ref_image average_image correlation_image=null max_proj_image=null "> -<polygon fill="none" points="277,-210.5 142,-210.5 142,-191.5 277,-191.5 277,-210.5" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="middle" x="209.5" y="-198.5">MotionCorrection.Summary</text> -</a> -</g> -</g> -<!-- MotionCorrection->MotionCorrection.Summary --> -<g class="edge" id="edge4"><title>MotionCorrection->MotionCorrection.Summary</title> -<path d="M225.092,-249.89C221.009,-237.348 215.57,-220.643 212.313,-210.64" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Segmentation --> -<g class="node" id="node23"><title>Segmentation</title> -<g id="a_node23"><a xlink:title="→ MotionCorrection → Curation "> -<ellipse cx="339.5" cy="-201" fill="#ff0000" fill-opacity="0.125490" rx="13" ry="13" stroke="#ff0000" stroke-opacity="0.125490"/> -<text fill="#7f0000" font-family="arial" font-size="12.00" text-anchor="middle" x="339.5" y="-197.9">Segmentation</text> -</a> -</g> -</g> -<!-- MotionCorrection->Segmentation --> -<g class="edge" id="edge5"><title>MotionCorrection->Segmentation</title> -<path d="M256.331,-251.214C279.373,-237.58 311.972,-218.29 328.689,-208.397" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- MaskClassification --> -<g class="node" id="node5"><title>MaskClassification</title> -<g id="a_node5"><a xlink:title="→ Segmentation → MaskClassificationMethod "> -<ellipse cx="467.5" cy="-134.5" fill="#ff0000" fill-opacity="0.125490" rx="13" ry="13" stroke="#ff0000" stroke-opacity="0.125490"/> -<text fill="#7f0000" font-family="arial" font-size="12.00" text-anchor="middle" x="467.5" y="-131.4">MaskClassification</text> -</a> -</g> -</g> -<!-- MaskClassification.MaskType --> -<g class="node" id="node20"><title>MaskClassification.MaskType</title> -<g id="a_node20"><a xlink:title="→ MaskClassification → Segmentation.Mask ------------------------------ → MaskType confidence=null "> -<polygon fill="none" points="540.5,-77.5 394.5,-77.5 394.5,-58.5 540.5,-58.5 540.5,-77.5" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="middle" x="467.5" y="-65.5">MaskClassification.MaskType</text> -</a> -</g> -</g> -<!-- MaskClassification->MaskClassification.MaskType --> -<g class="edge" id="edge6"><title>MaskClassification->MaskClassification.MaskType</title> -<path d="M467.5,-121.368C467.5,-108.669 467.5,-89.0398 467.5,-77.768" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Equipment --> -<g class="node" id="node6"><title>Equipment</title> -<g id="a_node6"><a xlink:title="scanner "> -<polygon fill="#00ff00" fill-opacity="0.188235" points="115,-560 42,-560 42,-525 115,-525 115,-560" stroke="#00ff00" stroke-opacity="0.188235"/> -<text fill="darkgreen" font-family="arial" font-size="12.00" text-anchor="start" text-decoration="underline" x="50" y="-540.4">Equipment</text> -</a> -</g> -</g> -<!-- Scan --> -<g class="node" id="node14"><title>Scan</title> -<g id="a_node14"><a xlink:title="→ Session scan_id ------------------------------ → Equipment → AcquisitionSoftware scan_notes="" "> -<polygon fill="#00ff00" fill-opacity="0.188235" points="224.5,-489 180.5,-489 180.5,-454 224.5,-454 224.5,-489" stroke="#00ff00" stroke-opacity="0.188235"/> -<text fill="darkgreen" font-family="arial" font-size="12.00" text-anchor="start" text-decoration="underline" x="188.5" y="-469.4">Scan</text> -</a> -</g> -</g> -<!-- Equipment->Scan --> -<g class="edge" id="edge7"><title>Equipment->Scan</title> -<path d="M108.201,-524.973C130.412,-512.613 160.296,-495.984 180.42,-484.787" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- ProcessingTask --> -<g class="node" id="node7"><title>ProcessingTask</title> -<g id="a_node7"><a xlink:title="→ Scan → ProcessingParamSet ------------------------------ processing_output_dir task_mode="load" "> -<polygon fill="#00ff00" fill-opacity="0.188235" points="378,-418 277,-418 277,-383 378,-383 378,-418" stroke="#00ff00" stroke-opacity="0.188235"/> -<text fill="darkgreen" font-family="arial" font-size="12.00" text-anchor="middle" x="327.5" y="-397.4">ProcessingTask</text> -</a> -</g> -</g> -<!-- Processing --> -<g class="node" id="node12"><title>Processing</title> -<g id="a_node12"><a xlink:title="→ ProcessingTask ------------------------------ processing_time "> -<ellipse cx="327.5" cy="-334" fill="#ff0000" fill-opacity="0.125490" rx="13" ry="13" stroke="#ff0000" stroke-opacity="0.125490"/> -<text fill="#7f0000" font-family="arial" font-size="12.00" text-anchor="middle" x="327.5" y="-330.9">Processing</text> -</a> -</g> -</g> -<!-- ProcessingTask->Processing --> -<g class="edge" id="edge8"><title>ProcessingTask->Processing</title> -<path d="M327.5,-382.89C327.5,-371.692 327.5,-357.175 327.5,-347.073" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="2"/> -</g> -<!-- MaskType --> -<g class="node" id="node8"><title>MaskType</title> -<g id="a_node8"><a xlink:title="mask_type "> -<polygon fill="#000000" fill-opacity="0.125490" points="605.5,-152 543.5,-152 543.5,-117 605.5,-117 605.5,-152" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="551.5" y="-133">MaskType</text> -</a> -</g> -</g> -<!-- MaskType->MaskClassification.MaskType --> -<g class="edge" id="edge9"><title>MaskType->MaskClassification.MaskType</title> -<path d="M546.945,-116.89C526.14,-104.348 498.427,-87.643 481.833,-77.6402" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Fluorescence --> -<g class="node" id="node10"><title>Fluorescence</title> -<g id="a_node10"><a xlink:title="→ Segmentation "> -<ellipse cx="225.5" cy="-134.5" fill="#ff0000" fill-opacity="0.125490" rx="13" ry="13" stroke="#ff0000" stroke-opacity="0.125490"/> -<text fill="#7f0000" font-family="arial" font-size="12.00" text-anchor="middle" x="225.5" y="-131.4">Fluorescence</text> -</a> -</g> -</g> -<!-- Fluorescence->Fluorescence.Trace --> -<g class="edge" id="edge10"><title>Fluorescence->Fluorescence.Trace</title> -<path d="M225.5,-121.368C225.5,-108.669 225.5,-89.0398 225.5,-77.768" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Activity --> -<g class="node" id="node21"><title>Activity</title> -<g id="a_node21"><a xlink:title="→ Fluorescence → ActivityExtractionMethod "> -<ellipse cx="129.5" cy="-68" fill="#ff0000" fill-opacity="0.125490" rx="13" ry="13" stroke="#ff0000" stroke-opacity="0.125490"/> -<text fill="#7f0000" font-family="arial" font-size="12.00" text-anchor="middle" x="129.5" y="-64.9">Activity</text> -</a> -</g> -</g> -<!-- Fluorescence->Activity --> -<g class="edge" id="edge11"><title>Fluorescence->Activity</title> -<path d="M214.86,-126.351C196.309,-113.887 158.275,-88.333 139.896,-75.985" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- AcquisitionSoftware --> -<g class="node" id="node11"><title>AcquisitionSoftware</title> -<g id="a_node11"><a xlink:title="acq_software "> -<polygon fill="#000000" fill-opacity="0.125490" points="235.5,-560 133.5,-560 133.5,-525 235.5,-525 235.5,-560" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="141.5" y="-541">AcquisitionSoftware</text> -</a> -</g> -</g> -<!-- AcquisitionSoftware->Scan --> -<g class="edge" id="edge12"><title>AcquisitionSoftware->Scan</title> -<path d="M188.857,-524.797C191.687,-513.949 195.361,-499.867 198.183,-489.049" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Processing->MotionCorrection --> -<g class="edge" id="edge13"><title>Processing->MotionCorrection</title> -<path d="M317.119,-326.097C302.164,-316.153 274.145,-297.522 254.02,-284.139" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="2"/> -</g> -<!-- Curation --> -<g class="node" id="node16"><title>Curation</title> -<g id="a_node16"><a xlink:title="→ Processing curation_id ------------------------------ curation_time curation_output_dir manual_curation curation_note="" "> -<polygon fill="#00ff00" fill-opacity="0.188235" points="376,-285 315,-285 315,-250 376,-250 376,-285" stroke="#00ff00" stroke-opacity="0.188235"/> -<text fill="darkgreen" font-family="arial" font-size="12.00" text-anchor="start" text-decoration="underline" x="323" y="-265.4">Curation</text> -</a> -</g> -</g> -<!-- Processing->Curation --> -<g class="edge" id="edge14"><title>Processing->Curation</title> -<path d="M330.801,-321.17C333.603,-311.132 337.655,-296.611 340.796,-285.357" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- ActivityExtractionMethod --> -<g class="node" id="node13"><title>ActivityExtractionMethod</title> -<g id="a_node13"><a xlink:title="extraction_method "> -<polygon fill="#000000" fill-opacity="0.125490" points="125,-152 0,-152 0,-117 125,-117 125,-152" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="8" y="-133">ActivityExtractionMethod</text> -</a> -</g> -</g> -<!-- ActivityExtractionMethod->Activity --> -<g class="edge" id="edge15"><title>ActivityExtractionMethod->Activity</title> -<path d="M79.7542,-116.89C92.7818,-104.348 110.134,-87.643 120.525,-77.6402" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Scan->ProcessingTask --> -<g class="edge" id="edge16"><title>Scan->ProcessingTask</title> -<path d="M224.56,-458.323C244.745,-447.181 274.793,-430.594 297.221,-418.214" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- ScanInfo --> -<g class="node" id="node24"><title>ScanInfo</title> -<g id="a_node24"><a xlink:title="→ Scan ------------------------------ nfields nchannels ndepths nframes nrois x y z fps bidirectional usecs_per_line=null fill_fraction=null "> -<ellipse cx="202.5" cy="-400.5" fill="#00007f" fill-opacity="0.250980" rx="38.5016" ry="17.5" stroke="#00007f" stroke-opacity="0.250980"/> -<text fill="#00007f" font-family="arial" font-size="12.00" text-anchor="middle" x="202.5" y="-397.4">ScanInfo</text> -</a> -</g> -</g> -<!-- Scan->ScanInfo --> -<g class="edge" id="edge17"><title>Scan->ScanInfo</title> -<path d="M202.5,-453.797C202.5,-442.949 202.5,-428.867 202.5,-418.049" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="2"/> -</g> -<!-- Subject --> -<g class="node" id="node15"><title>Subject</title> -<g id="a_node15"><a xlink:title="subject ------------------------------ sex subject_birth_date subject_description="" "> -<polygon fill="#00ff00" fill-opacity="0.188235" points="311.5,-631 255.5,-631 255.5,-596 311.5,-596 311.5,-631" stroke="#00ff00" stroke-opacity="0.188235"/> -<text fill="darkgreen" font-family="arial" font-size="12.00" text-anchor="start" text-decoration="underline" x="263.5" y="-611.4">Subject</text> -</a> -</g> -</g> -<!-- Session --> -<g class="node" id="node25"><title>Session</title> -<g id="a_node25"><a xlink:title="→ Subject session_datetime "> -<polygon fill="#00ff00" fill-opacity="0.188235" points="313,-560 254,-560 254,-525 313,-525 313,-560" stroke="#00ff00" stroke-opacity="0.188235"/> -<text fill="darkgreen" font-family="arial" font-size="12.00" text-anchor="start" text-decoration="underline" x="262" y="-540.4">Session</text> -</a> -</g> -</g> -<!-- Subject->Session --> -<g class="edge" id="edge18"><title>Subject->Session</title> -<path d="M283.5,-595.797C283.5,-584.949 283.5,-570.867 283.5,-560.049" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Curation->Segmentation --> -<g class="edge" id="edge19"><title>Curation->Segmentation</title> -<path d="M343.955,-249.89C342.913,-238.692 341.563,-224.175 340.623,-214.073" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="2"/> -</g> -<!-- ProcessingParamSet --> -<g class="node" id="node17"><title>ProcessingParamSet</title> -<g id="a_node17"><a xlink:title="paramset_idx ------------------------------ → ProcessingMethod paramset_desc param_set_hash params UNIQUE INDEX (param_set_hash) "> -<polygon fill="#000000" fill-opacity="0.125490" points="417,-489 308,-489 308,-454 417,-454 417,-489" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="316" y="-470">ProcessingParamSet</text> -</a> -</g> -</g> -<!-- ProcessingParamSet->ProcessingTask --> -<g class="edge" id="edge20"><title>ProcessingParamSet->ProcessingTask</title> -<path d="M354.027,-453.797C348.525,-442.949 341.382,-428.867 335.895,-418.049" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Channel --> -<g class="node" id="node18"><title>Channel</title> -<g id="a_node18"><a xlink:title="channel "> -<polygon fill="#000000" fill-opacity="0.125490" points="125.5,-418 73.5,-418 73.5,-383 125.5,-383 125.5,-418" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="81.5" y="-399">Channel</text> -</a> -</g> -</g> -<!-- Channel->1 --> -<g class="edge" id="edge22"><title>Channel->1</title> -<path d="M99.3733,-382.909C99.1232,-350.137 98.5833,-279.415 98.5086,-269.631" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Channel->0 --> -<g class="edge" id="edge21"><title>Channel->0</title> -<path d="M96.6864,-382.661C89.2049,-337.885 69.2202,-218.28 66.7513,-203.504" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Channel->2 --> -<g class="edge" id="edge23"><title>Channel->2</title> -<path d="M109.028,-382.89C118.621,-366.168 132.459,-342.044 135.769,-336.274" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Activity.Trace --> -<g class="node" id="node28"><title>Activity.Trace</title> -<g id="a_node28"><a xlink:title="→ Activity → Fluorescence.Trace ------------------------------ activity_trace "> -<polygon fill="none" points="167,-19 92,-19 92,-0 167,-0 167,-19" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="middle" x="129.5" y="-7">Activity.Trace</text> -</a> -</g> -</g> -<!-- Fluorescence.Trace->Activity.Trace --> -<g class="edge" id="edge24"><title>Fluorescence.Trace->Activity.Trace</title> -<path d="M210.92,-58.4189C192.876,-47.7994 162.324,-29.8185 144.218,-19.1619" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Activity->Activity.Trace --> -<g class="edge" id="edge25"><title>Activity->Activity.Trace</title> -<path d="M129.5,-54.7403C129.5,-43.9652 129.5,-28.5623 129.5,-19.0639" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Segmentation.Mask->Fluorescence.Trace --> -<g class="edge" id="edge26"><title>Segmentation.Mask->Fluorescence.Trace</title> -<path d="M324.125,-124.801C302.246,-112.422 262.376,-89.8639 240.631,-77.5609" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Segmentation.Mask->MaskClassification.MaskType --> -<g class="edge" id="edge27"><title>Segmentation.Mask->MaskClassification.MaskType</title> -<path d="M356.497,-124.935C380.979,-112.598 425.911,-89.9567 450.432,-77.6005" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Segmentation->MaskClassification --> -<g class="edge" id="edge28"><title>Segmentation->MaskClassification</title> -<path d="M350.859,-194.276C375.088,-182.067 431.613,-153.584 455.982,-141.304" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Segmentation->Fluorescence --> -<g class="edge" id="edge29"><title>Segmentation->Fluorescence</title> -<path d="M328.569,-193.815C306.835,-181.518 258.409,-154.12 236.565,-141.761" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="2"/> -</g> -<!-- Segmentation->Segmentation.Mask --> -<g class="edge" id="edge30"><title>Segmentation->Segmentation.Mask</title> -<path d="M339.5,-187.868C339.5,-175.169 339.5,-155.54 339.5,-144.268" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- ScanInfo.Field --> -<g class="node" id="node26"><title>ScanInfo.Field</title> -<g id="a_node26"><a xlink:title="→ ScanInfo field_idx ------------------------------ px_height px_width um_height=null um_width=null field_x field_y field_z delay_image=null "> -<polygon fill="none" points="241.5,-343.5 163.5,-343.5 163.5,-324.5 241.5,-324.5 241.5,-343.5" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="171.5" y="-332.5">ScanInfo.Field</text> -</a> -</g> -</g> -<!-- ScanInfo->ScanInfo.Field --> -<g class="edge" id="edge31"><title>ScanInfo->ScanInfo.Field</title> -<path d="M202.5,-382.89C202.5,-370.348 202.5,-353.643 202.5,-343.64" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- Session->Scan --> -<g class="edge" id="edge32"><title>Session->Scan</title> -<path d="M263.892,-524.797C251.157,-513.949 234.626,-499.867 221.927,-489.049" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- ScanInfo.Field->MotionCorrection.Summary --> -<g class="edge" id="edge33"><title>ScanInfo.Field->MotionCorrection.Summary</title> -<path d="M189.02,-324.198C177.405,-315.732 161.445,-301.768 154.5,-285 148.547,-270.629 148.063,-264.161 154.5,-250 162.471,-232.463 180.366,-218.829 193.591,-210.646" fill="none" stroke="#000000" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -<!-- ProcessingMethod --> -<g class="node" id="node27"><title>ProcessingMethod</title> -<g id="a_node27"><a xlink:title="processing_method ------------------------------ processing_method_desc "> -<polygon fill="#000000" fill-opacity="0.125490" points="429.5,-560 331.5,-560 331.5,-525 429.5,-525 429.5,-560" stroke="none"/> -<text font-family="arial" font-size="10.00" text-anchor="start" text-decoration="underline" x="339.5" y="-541">ProcessingMethod</text> -</a> -</g> -</g> -<!-- ProcessingMethod->ProcessingParamSet --> -<g class="edge" id="edge34"><title>ProcessingMethod->ProcessingParamSet</title> -<path d="M376.143,-524.797C373.313,-513.949 369.639,-499.867 366.817,-489.049" fill="none" stroke="#000000" stroke-dasharray="5,2" stroke-opacity="0.250980" stroke-width="0.75"/> -</g> -</g> -</svg> \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b435a59..6a497d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1 @@ -datajoint -scipy -imreg_dft -scikit-learn -h5py -opencv-contrib-python-headless -scanreader @ git+https://github.com/atlab/scanreader.git +datajoint>=0.13.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 9894256..e402c72 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,12 @@ #!/usr/bin/env python from setuptools import setup, find_packages from os import path -import sys -pkg_name = 'element_miniscope' +pkg_name = next(p for p in find_packages() if '.' not in p) here = path.abspath(path.dirname(__file__)) -long_description = """" -DataJoint Element for miniscope calcium imaging data. -""" +with open(path.join(here, 'README.md'), 'r') as f: + long_description = f.read() with open(path.join(here, 'requirements.txt')) as f: requirements = f.read().splitlines() @@ -17,15 +15,17 @@ exec(f.read()) setup( - name='element-miniscope', + name=pkg_name.replace('_', '-'), version=__version__, description="Miniscope DataJoint Element", long_description=long_description, - author='DataJoint NEURO', - author_email='info@vathes.com', + long_description_content_type='text/markdown', + author='DataJoint', + author_email='info@datajoint.com', license='MIT', - url='https://github.com/datajoint/element-miniscope', + url=f'https://github.com/datajoint/{pkg_name.replace("_", "-")}', keywords='neuroscience calcium-imaging science datajoint miniscope', packages=find_packages(exclude=['contrib', 'docs', 'tests*']), + scripts=[], install_requires=requirements, )