pax_global_header00006660000000000000000000000064146255256750014533gustar00rootroot0000000000000052 comment=061b6e199fd2464750c8e59fca899fe3353ddb4e q2-diversity-lib-2024.5.0/000077500000000000000000000000001462552567500150735ustar00rootroot00000000000000q2-diversity-lib-2024.5.0/.coveragerc000066400000000000000000000003201462552567500172070ustar00rootroot00000000000000[run] branch = True omit = */tests* */__init__.py q2_diversity_lib/_version.py versioneer.py [report] omit = */tests* */__init__.py q2_diversity_lib/_version.py versioneer.py q2-diversity-lib-2024.5.0/.gitattributes000066400000000000000000000000521462552567500177630ustar00rootroot00000000000000q2_diversity_lib/_version.py export-subst q2-diversity-lib-2024.5.0/.github/000077500000000000000000000000001462552567500164335ustar00rootroot00000000000000q2-diversity-lib-2024.5.0/.github/CONTRIBUTING.md000066400000000000000000000015131462552567500206640ustar00rootroot00000000000000# Contributing to this project Thanks for thinking of us :heart: :tada: - we would love a helping hand! ## I just have a question > Note: Please don't file an issue to ask a question. You'll get faster results > by using the resources below. ### QIIME 2 Users Check out the [User Docs](https://docs.qiime2.org) - there are many tutorials, walkthroughs, and guides available. If you still need help, please visit us at the [QIIME 2 Forum](https://forum.qiime2.org/c/user-support). ### QIIME 2 Developers Check out the [Developer Docs](https://dev.qiime2.org) - there are many tutorials, walkthroughs, and guides available. If you still need help, please visit us at the [QIIME 2 Forum](https://forum.qiime2.org/c/dev-discussion). This document is based heavily on the following: https://github.com/atom/atom/blob/master/CONTRIBUTING.md q2-diversity-lib-2024.5.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001462552567500206165ustar00rootroot00000000000000q2-diversity-lib-2024.5.0/.github/ISSUE_TEMPLATE/1-user-need-help.md000066400000000000000000000006111462552567500241110ustar00rootroot00000000000000--- name: I am a user and I need help with QIIME 2... about: I am using QIIME 2 and have a question or am experiencing a problem --- Have you had a chance to check out the docs? https://docs.qiime2.org There are many tutorials, walkthroughs, and guides available. If you still need help, please visit: https://forum.qiime2.org/c/user-support Help requests filed here will not be answered. q2-diversity-lib-2024.5.0/.github/ISSUE_TEMPLATE/2-dev-need-help.md000066400000000000000000000005641462552567500237210ustar00rootroot00000000000000--- name: I am a developer and I need help with QIIME 2... about: I am developing a QIIME 2 plugin or interface and have a question or a problem --- Have you had a chance to check out the developer docs? https://dev.qiime2.org There are many tutorials, walkthroughs, and guides available. If you still need help, please visit: https://forum.qiime2.org/c/dev-discussion q2-diversity-lib-2024.5.0/.github/ISSUE_TEMPLATE/3-found-bug.md000066400000000000000000000017421462552567500231720ustar00rootroot00000000000000--- name: I am a developer and I found a bug... about: I am a developer and I found a bug that I can describe --- **Bug Description** A clear and concise description of what the bug is. **Steps to reproduce the behavior** 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Computation Environment** - OS: [e.g. macOS High Sierra] - QIIME 2 Release [e.g. 2018.6] **Questions** 1. An enumerated list with any questions about the problem here. 2. If not applicable, please delete this section. **Comments** 1. An enumerated list with any other context or comments about the problem here. 2. If not applicable, please delete this section. **References** 1. An enumerated list of links to relevant references, including forum posts, stack overflow, etc. 2. If not applicable, please delete this section. q2-diversity-lib-2024.5.0/.github/ISSUE_TEMPLATE/4-make-better.md000066400000000000000000000015321462552567500235020ustar00rootroot00000000000000--- name: I am a developer and I have an idea for an improvement... about: I am a developer and I have an idea for an improvement to existing functionality --- **Improvement Description** A clear and concise description of what the improvement is. **Current Behavior** Please provide a brief description of the current behavior. **Proposed Behavior** Please provide a brief description of the proposed behavior. **Questions** 1. An enumerated list of questions related to the proposal. 2. If not applicable, please delete this section. **Comments** 1. An enumerated list of comments related to the proposal that don't fit anywhere else. 2. If not applicable, please delete this section. **References** 1. An enumerated list of links to relevant references, including forum posts, stack overflow, etc. 2. If not applicable, please delete this section. q2-diversity-lib-2024.5.0/.github/ISSUE_TEMPLATE/5-make-new.md000066400000000000000000000015131462552567500230060ustar00rootroot00000000000000--- name: I am a developer and I have an idea for a new feature... about: I am a developer and I have an idea for new functionality --- **Addition Description** A clear and concise description of what the addition is. **Current Behavior** Please provide a brief description of the current behavior, if applicable. **Proposed Behavior** Please provide a brief description of the proposed behavior. **Questions** 1. An enumerated list of questions related to the proposal. 2. If not applicable, please delete this section. **Comments** 1. An enumerated list of comments related to the proposal that don't fit anywhere else. 2. If not applicable, please delete this section. **References** 1. An enumerated list of links to relevant references, including forum posts, stack overflow, etc. 2. If not applicable, please delete this section. q2-diversity-lib-2024.5.0/.github/ISSUE_TEMPLATE/6-where-to-go.md000066400000000000000000000100111462552567500234310ustar00rootroot00000000000000--- name: I don't know where to file my issue... about: I am a developer and I don't know which repo to file this in --- The repos within the QIIME 2 GitHub Organization are listed below, with a brief description about the repo. Sorted alphabetically by repo name. - The CI automation engine that builds and distributes QIIME 2 https://github.com/qiime2/busywork/issues - A Concourse resource for working with conda https://github.com/qiime2/conda-channel-resource/issues - Web app for vanity URLs for QIIME 2 data assets https://github.com/qiime2/data.qiime2.org/issues - The Developer Documentation https://github.com/qiime2/dev-docs/issues - A discourse plugin for handling queued/unqueued topics https://github.com/qiime2/discourse-unhandled-tagger/issues - The User Documentation https://github.com/qiime2/docs/issues - Rendered QIIME 2 environment files for conda https://github.com/qiime2/environment-files/issues - Google Sheets Add-On for validating tabular data https://github.com/qiime2/Keemei/issues - A docker image for linux-based busywork workers https://github.com/qiime2/linux-worker-docker/issues - Official project logos https://github.com/qiime2/logos/issues - The q2-alignment plugin https://github.com/qiime2/q2-alignment/issues - The q2-composition plugin https://github.com/qiime2/q2-composition/issues - The q2-cutadapt plugin https://github.com/qiime2/q2-cutadapt/issues - The q2-dada2 plugin https://github.com/qiime2/q2-dada2/issues - The q2-deblur plugin https://github.com/qiime2/q2-deblur/issues - The q2-demux plugin https://github.com/qiime2/q2-demux/issues - The q2-diversity plugin https://github.com/qiime2/q2-diversity/issues - The q2-diversity-lib plugin https://github.com/qiime2/q2-diversity-lib/issues - The q2-emperor plugin https://github.com/qiime2/q2-emperor/issues - The q2-feature-classifier plugin https://github.com/qiime2/q2-feature-classifier/issues - The q2-feature-table plugin https://github.com/qiime2/q2-feature-table/issues - The q2-fragment-insertion plugin https://github.com/qiime2/q2-fragment-insertion/issues - The q2-gneiss plugin https://github.com/qiime2/q2-gneiss/issues - The q2-longitudinal plugin https://github.com/qiime2/q2-longitudinal/issues - The q2-metadata plugin https://github.com/qiime2/q2-metadata/issues - The q2-phylogeny plugin https://github.com/qiime2/q2-phylogeny/issues - The q2-quality-control plugin https://github.com/qiime2/q2-quality-control/issues - The q2-quality-filter plugin https://github.com/qiime2/q2-quality-filter/issues - The q2-sample-classifier plugin https://github.com/qiime2/q2-sample-classifier/issues - The q2-shogun plugin https://github.com/qiime2/q2-shogun/issues - The q2-taxa plugin https://github.com/qiime2/q2-taxa/issues - The q2-types plugin https://github.com/qiime2/q2-types/issues - The q2-vsearch plugin https://github.com/qiime2/q2-vsearch/issues - The CLI interface https://github.com/qiime2/q2cli/issues - The prototype CWL interface https://github.com/qiime2/q2cwl/issues - The prototype Galaxy interface https://github.com/qiime2/q2galaxy/issues - An internal tool for ensuring header text and copyrights are present https://github.com/qiime2/q2lint/issues - The prototype GUI interface https://github.com/qiime2/q2studio/issues - A base template for use in official QIIME 2 plugins https://github.com/qiime2/q2templates/issues - The read-only web interface at view.qiime2.org https://github.com/qiime2/q2view/issues - The QIIME 2 homepage at qiime2.org https://github.com/qiime2/qiime2.github.io/issues - The QIIME 2 framework https://github.com/qiime2/qiime2/issues - Centralized templates for repo assets https://github.com/qiime2/template-repo/issues - Scripts for building QIIME 2 VMs https://github.com/qiime2/vm-playbooks/issues - Scripts for building QIIME 2 workshop clusters https://github.com/qiime2/workshop-playbooks/issues - The web app that runs workshops.qiime2.org https://github.com/qiime2/workshops.qiime2.org/issues q2-diversity-lib-2024.5.0/.github/SUPPORT.md000066400000000000000000000122421462552567500201320ustar00rootroot00000000000000# QIIME 2 Users Check out the [User Docs](https://docs.qiime2.org) - there are many tutorials, walkthroughs, and guides available. If you still need help, please visit us at the [QIIME 2 Forum](https://forum.qiime2.org/c/user-support). # QIIME 2 Developers Check out the [Developer Docs](https://dev.qiime2.org) - there are many tutorials, walkthroughs, and guides available. If you still need help, please visit us at the [QIIME 2 Forum](https://forum.qiime2.org/c/dev-discussion). # General Bug/Issue Triage Discussion ![rubric](./rubric.png?raw=true) # Projects/Repositories in the QIIME 2 GitHub Organization Sorted alphabetically by repo name. - [busywork](https://github.com/qiime2/busywork/issues) | The CI automation engine that builds and distributes QIIME 2 - [conda-channel-resource](https://github.com/qiime2/conda-channel-resource/issues) | A Concourse resource for working with conda - [data.qiime2.org](https://github.com/qiime2/data.qiime2.org/issues) | Web app for vanity URLs for QIIME 2 data assets - [dev-docs](https://github.com/qiime2/dev-docs/issues) | The Developer Documentation - [discourse-unhandled-tagger](https://github.com/qiime2/discourse-unhandled-tagger/issues) | A discourse plugin for handling queued/unqueued topics - [docs](https://github.com/qiime2/docs/issues) | The User Documentation - [environment-files](https://github.com/qiime2/environment-files/issues) | Rendered QIIME 2 environment files for conda - [Keemei](https://github.com/qiime2/Keemei/issues) | Google Sheets Add-On for validating tabular data - [linux-worker-docker](https://github.com/qiime2/linux-worker-docker/issues) | A docker image for linux-based busywork workers - [logos](https://github.com/qiime2/logos/issues) | Official project logos - [q2-alignment](https://github.com/qiime2/q2-alignment/issues) | The q2-alignment plugin - [q2-composition](https://github.com/qiime2/q2-composition/issues) | The q2-composition plugin - [q2-cutadapt](https://github.com/qiime2/q2-cutadapt/issues) | The q2-cutadapt plugin - [q2-dada2](https://github.com/qiime2/q2-dada2/issues) | The q2-dada2 plugin - [q2-deblur](https://github.com/qiime2/q2-deblur/issues) | The q2-deblur plugin - [q2-demux](https://github.com/qiime2/q2-demux/issues) | The q2-demux plugin - [q2-diversity](https://github.com/qiime2/q2-diversity/issues) | The q2-diversity plugin - [q2-diversity-lib](https://github.com/qiime2/q2-diversity-lib/issues) | The q2-diversity-lib plugin - [q2-emperor](https://github.com/qiime2/q2-emperor/issues) | The q2-emperor plugin - [q2-feature-classifier](https://github.com/qiime2/q2-feature-classifier/issues) | The q2-feature-classifier plugin - [q2-feature-table](https://github.com/qiime2/q2-feature-table/issues) | The q2-feature-table plugin - [q2-fragment-insertion](https://github.com/qiime2/q2-fragment-insertion/issues) | The q2-fragment-insertion plugin - [q2-gneiss](https://github.com/qiime2/q2-gneiss/issues) | The q2-gneiss plugin - [q2-longitudinal](https://github.com/qiime2/q2-longitudinal/issues) | The q2-longitudinal plugin - [q2-metadata](https://github.com/qiime2/q2-metadata/issues) | The q2-metadata plugin - [q2-phylogeny](https://github.com/qiime2/q2-phylogeny/issues) | The q2-phylogeny plugin - [q2-quality-control](https://github.com/qiime2/q2-quality-control/issues) | The q2-quality-control plugin - [q2-quality-filter](https://github.com/qiime2/q2-quality-filter/issues) | The q2-quality-filter plugin - [q2-sample-classifier](https://github.com/qiime2/q2-sample-classifier/issues) | The q2-sample-classifier plugin - [q2-shogun](https://github.com/qiime2/q2-shogun/issues) | The q2-shogun plugin - [q2-taxa](https://github.com/qiime2/q2-taxa/issues) | The q2-taxa plugin - [q2-types](https://github.com/qiime2/q2-types/issues) | The q2-types plugin - [q2-vsearch](https://github.com/qiime2/q2-vsearch/issues) | The q2-vsearch plugin - [q2cli](https://github.com/qiime2/q2cli/issues) | The CLI interface - [q2cwl](https://github.com/qiime2/q2cwl/issues) | The prototype CWL interface - [q2galaxy](https://github.com/qiime2/q2galaxy/issues) | The prototype Galaxy interface - [q2lint](https://github.com/qiime2/q2lint/issues) | An internal tool for ensuring header text and copyrights are present - [q2studio](https://github.com/qiime2/q2studio/issues) | The prototype GUI interface - [q2templates](https://github.com/qiime2/q2templates/issues) | A base template for use in official QIIME 2 plugins - [q2view](https://github.com/qiime2/q2view/issues) | The read-only web interface at view.qiime2.org - [qiime2.github.io](https://github.com/qiime2/qiime2.github.io/issues) | The QIIME 2 homepage at qiime2.org - [qiime2](https://github.com/qiime2/qiime2/issues) | The QIIME 2 framework - [template-repo](https://github.com/qiime2/template-repo/issues) | Centralized templates for repo assets - [vm-playbooks](https://github.com/qiime2/vm-playbooks/issues) | Scripts for building QIIME 2 VMs - [workshop-playbooks](https://github.com/qiime2/workshop-playbooks/issues) | Scripts for building QIIME 2 workshop clusters - [workshops.qiime2.org](https://github.com/qiime2/workshops.qiime2.org/issues) | The web app that runs workshops.qiime2.org q2-diversity-lib-2024.5.0/.github/pull_request_template.md000066400000000000000000000006121462552567500233730ustar00rootroot00000000000000Brief summary of the Pull Request, including any issues it may fix using the GitHub closing syntax: https://help.github.com/articles/closing-issues-using-keywords/ Also, include any co-authors or contributors using the GitHub coauthor tag: https://help.github.com/articles/creating-a-commit-with-multiple-authors/ --- Include any questions for reviewers, screenshots, sample outputs, etc. q2-diversity-lib-2024.5.0/.github/rubric.png000066400000000000000000007014131462552567500204350ustar00rootroot00000000000000PNG  IHDR,4\sBIT|d pHYs.#.#x?vtEXtSoftwarewww.inkscape.org< IDATxw|g3AޫEEQvgmKhQRD.#g$ G)9IIr~vkxr\7ޯ\b0 R1NE/zQ@G E/z'w:~9 .VhhD?^Kg7o78s7HG^{5]~]v ӧOլYD ^-ZٳgW_TB iٲeںufR)//_lRl߾ݤIs8#8ѣGرcٰakQ ___۷Of (p&I8p@sIO5ydXTRZ~ҦMk7oZpDO<1ӧO(8!?0c2dȠs,fݺu_6 8 2?sI @C ޽{uСfIZj& ilϟ߬EILR^DԬYq]]]`I&Ѿ6vUV5o\TD$&~<н{%6p^DGGk֭˓'O/Sڵkhh#^QF &:t\]/TՕ={ve˖M/[l8cǎ%.aÆI ᅲWڴi#F)+)<<\&MҡC8$^dN>mR7n$Y*Toh?^@\UTщ'4tP.]Z3gͫ-[jӦMZf0=ŋMw!;֭ۗ`^q 7|iĚ3gNrN/p6mq$=iӦD Klc׮]K4(z1P ><Ȟ=j֬`gϞ%Y|֋رc; IMMd4k,$ zSLÇ; Id̘Ѭ#GԈ#e\~)z)׉''$w(z6{_uѣ6ͥ\rʔ)SIQh`7ne˖zyr8bŊY4\:t蠀UN/ N%ɝJ,iXQVR 4l0ruw*g͚k׮m+][-Zӧ;8\ !OddgϮGd|k׮ڵ-j9<{Q۶mu=O,XP+VN4ӧOu1*((Haaazː! (E\rJ>}2g`>}ZgΜӧ+W`0_q\\\%K+WN5kT9$ϘԩS:}N:]qSM0A3fPtt(z{xCpBmڴy`0hڿ6mhTMݻw~zmذAғ'OL%Jyj۶*Tę:K.i͚5ڼy;(ƹB jٲڵkB %q҃b[Ew/FGFFjŚ]t<$???EEEI8>vzRѢEuʕ$Uzu |ڷooҽfS5jO?5ڶdXª6m'o׮]2ev!k_?aÆ=zj׮m\֋}I' @;vɓ'M#Oҷ~kl9z:viӦiѢE*W\#""gIh@s(^x큁:tlbUѶm۴m65o\S\̚/oݺufIү_O>5y̍7ԡCi[bV\_:tf͚e.]d$I|'Ol; Kwv=zƍ?~ϯݻ믿Klٲ7oM ^v1UZU~m̟,[L˗iU7oVٲegƭ^Z~~~6/xܹS-Z0~;w֭[Z˗/5-Q'EӮq#""d/_^=z͛7,֊+;tD[ʕf]|Yyn*___b={L}_|amک`ѣյkW=~8...j֬vء7n(22RW^UlYݻwO5?l-v%kNϞ=x_ՆY'{R7'ojΜ9ѣcך5k4zh3Ʀw~-\P}1.q,W\ʕ+5j%KW^&7ne˦}iҘ1c#Gɓ13G*TP`N:PhhN:G***ʤ8UVUժUU^8m CK.Uv^z/_>uI۷ט1c4mڴD犈PvcլYӤչsgX5ٳgmV ,ŋSddU󹹹QF*U1c(,,L>ԑ#Gt9֑iӪv*VMs1X{, ՛0a&N9TREWV맟~RVL*R :T|MK}G&O&L~I/_N:%`0M6Z~IsΝ['OTk׮ԩSڸq:dϟ_|>C3fnܸϟ'8WZo߾ߵh"]~\,Y]&`0رcF>cmݺլ 2) *\Kn߾QF~0+͛7Ox͛75x߿&M$oooQHFi3gNmٲE+Vx /_^>LouIK.ѾϞ=Sum(UN8!wY~ʖ-4ᅰ-[V'N0{ܫݻ9rmsuuՎ;۷QF&iժɅ/dɒ&رمM6K.hLƌg2eѣչs؝D HtTsxչsg?WZJH.]|rLlx.]TىbJKX6mژK@@/^hyjȐ!&oݳhܫ*, 6,ނ׃ԭ[7 ^4j(s*VTbr+Wȑ#&-Zhҥfy!,,쥂Wٵ~7L+O< {zzjŊ*UE:ɓ't5egbΓ1cd%  ֐!C}xﻱQFrcWX=c^UNO6ͤЀ>}z9v- qx^^^ 矛DZYyy v*lRժU3{ܿjFRIt)=<<4vXs4iEc9~Fڷoϑ /W/{6m(**1ϟ?7-IPʫxfpݛhٳ[nf-o6m2|ʚ5Ѷ˗/kܹ&LjoXBׯ_o1͛77{̿ 2DF֭X///w6k,~&SM74{ܬYnU g}j^`['%@dɢ ?P͚5%Çk&_j]frs{!wfaÆYtҥKM>јH\2sOOO?qӧOWddqyGFFj֭fDZ3w߿uY9r䈷=W\:zƌ*Upz7ճgO=zTY Zn%K=nڴi={y޻woI QuujnGh ѣG͚;gΜfcUvwĈf/IgVLLEc|_~{}c,aB.]#G}'Oj…*S,͑ZtY``֭[gQUVɓ'/=K>:vh| PbŊZ~4qD(Q"c.[,>7n4{^KW6{LttM[Z5ըQ_wEڵ+u&xwRJ/]dq\YfUjK:t`1-wy֮];eɒŢ@ҡ0[5n8;wN>e˖-Ib>}Z.\Hρ̞7$$侏?̙3GYw9Ң(`0y>dȐ%} :Tk׮5;γgt]YAVe5zh;vL~~~f9y݃m@D `*Uh֬Yy6mڤVZæ1lH޽{+cƌʚ5kx{{+s2d={fQ?6ofTxqcڵK&ҥKokv<<<4j(|_xxVXy>}̎ 샢fͪ:wFiAAA]ry ҥrav˗| <˔)zหW*Zhrz^{qv8EoOOOu>(zYlYY'!%JД)SѣG9֤g3f4hvOj…;}{v,Y$8?oKy{{%hWh?cGnݚ`Mj]t)ޝ@I;K,<./WWW 4(-=~pڴi]Ec͕?~ġf_њ:u͛og- ۢ<}P't>K5jΝ;o|_Bw4Yzz-Cյk_sm޼YZ2-_>>۷Oϟ~}%޽;N>}X^DrUw1 ()+V̤~E`NME#F=&""],>ԏ?_>}ڽU@sRiԨQfwzuX" `0ԧH"]MrI8$XQ$)yxx\rȑCٳg7;v@@@GJѼys/^q͋?ĹW]7k7x<9>%gΜ޽LEFFjɒ%qk yQ'ц/㏊C6x"""WNx\\\TJcĉfK6l_ 6H2~a˖-޹UP!̙\8p3fҦMk֘gj˖-駟tΝҦM]2E(z0eWӭ[n:;d;w9JU V%c`ھ}Ec}zMڵ|||7sLi5h rUcu%… ?~!e3(o|SNfꫯwyޢE ȑ;Nä#ƍ_tСDt)Ѽ}YƦŒuQƍS*UtY驁=?,I:y*U}ltګ;ِ!C,Sox IDAT̙?ƍho^ϟ$O*[n޼ia_~~~5c խ[Ws-ZTڵ3{ܫ"E^z쏢8tҙO>QPPPrƍDRرwNYr$M8Ѥ3{l}W/=ԛoi/[nVkYTx!cƌyYӧOe+W.Oy瞽kƍ/=H>HֽիIc@A IBCCѣGIxӧOO>Znŋ>Sg}f~ &˪_5l0_OV>}z͞=[7o8gԮ];-^8N۔)S,*R<~آ<ý{C㚲KU&LPDDDי3gV…͎oJR˖--&M}Q''O?~\M4I̙3-O>QM]|EL4ɢbbb4h E2eʨ哐bŊyO6guyѣg0ԵkW8q챏?{ァiرj֬i,޽{q}9sqձ-AD]6qM6+s?;6m9sZ/^DrmVztMhʔ)EذaC97n?աCM4)ν>|ԩٳgiK.VXaSXټЮ];UL=|P5kԆ Lk׮]q (`)-.&&&G5cҤIo~z#o޼f9poΝ;3k˗Wƍ-ۧO싢8K^\\r5k?nqgϪaÆC_ʕ+-1|+Vq111?~jժk*,,,N(:tHzoN)SXT 2UUzu2fyKNk֬QƌnZmڴѮ]ӧOm65iDjR```>iҤ-:F޽{jժ._lXI5j(G4Gdd>3M>ݢϟ?׻ᆱ 6\}Uƍ4 jٲ[]rEu6lؠ GqܹTbQƌ71 R l:-R^,?~ 8P)h[nСCocǎD_,YRwz'R``jժ[nY< .ܹsMWPPP=6L_qMqFjʬ15jЁl4ib=W*^rȡhݿ_/^LpNWWW-]T:u2)u9׆ lr|g,YԨQ#UREEU TLeϞݢBBBt1ݹsGGѺut59Sҥ?~e͚U+V4il g<)R;̙3t!SjĈ6E/p"ǎ3wb˧ҥK+_|򒋋BBB#ԩSq5lP+WT֬Ym_@@{=]r&{Zh#&&F%KŋMfi&Iٲe!\\\4sL}&?~&MYϢEԣGƮ]Vm۶qF*Tn'O[oeՎhǎ*W޳g֭kRߌ3ڵk=OR&MGɽ^z_~w5>I>>3 6LnnnVBR}ڽ{5EǏg}f.Ç&ϗ/Zld4iDP۶mu…$#IZxڷoqe/.B iKj:uFOon(xq8wz+WNuQ5k,rI&wﮀ9Ҧrȡ]vi5kV^Z ^/tU>>>&8pݓw_˖-'NhĈJ6m(VG+ >\SLnnn۷>dƏhWWW <8II8ƍy/_>-YDGUʕ%S0ڼynݪ5j,~|4~x]pA/Vm61...8p._qY|ϒy{'ّԀ>}zԩSuyS6?שSTR% ˌ9RwVٲe-.]:mV-[6go4n8ɋo iq8ǏB 9pĈرc;sN::qݻgRӫ|]5kʕ+5~ٳgڸq6oެ;vfԭ[7KEM,Mw=(P@ӧ,X`Ǭ'$$D?l٢={$1%JP޽եKw>秥K/5!W\Y4h6m$6mJϝ;w~vE/p2A&LPcwuYƍp߿P`0(}ʘ1ͫH"I~bbbt=zT/^իWu-EEE)44Tʚ5gϮrʩjժz7hgѣw^˗OSb=_|ʙ3gr(IzN>G*((Hׯ_۷0e͚UYfUΜ9UR%UZU JaWҥKzٳ+w)⿣`Pٲeuԩ8mK_e#JmQSXhzw}g9H]R֯IСCq̙S;vLlQû{VX~(OOdE/8s*""gL[`G111u넇kĉX ,jժiƌ<5jҧO;rOĉuM͝;W>bbbԬY3>r~wkq|׺sKϊ-$y~\ j]矪Y"##վ}{-[LiҤIRu֩M6F۲e˦k׮tGŋUB>sqqy$N`>T)IZj5jR1ۼysm_c U6m|ݻw`SPPKvڥƍÇɔUsۗ/_.XÆ uɗ .)S$Y~ p!@[dwo{ ce˖͎YNJҙ3gmwqqQJt%dɒI&HP``*TG%د\rڹsgnRիСCfK6~Wխ[7 )$(u1т$8qB׽{Y[o=&C ڰa/E/$2~x$WWWm۶rww7ɓzwt{ 8P&/^8{/ )E/$pYM2ETL:tH۶mիyx 7OVݺuum{j+VLVR,YQFѣ*Wɉ;,mذ!NfjѢ"""-^vޭܹs#TΝ;Z|ۧ =}T3fT…UNuA>>>ɝ&#^I(*** nݪVZٳgFK,ݻw+gΜI"C`6lM6)]tF/\u͛v u(|X@ аaCmܸ(z,D `^) / Cr')**Jо}}/Yvޭ9s1K@25k,]zլA }xŋkʝ; 3H](z$VZ:w$)cƌݻ7nB Ç:~֭[;v(&&&޹E/dzi޽jժiʕ+ǏרQk׮xkr'؂W͚5{x ^T|yܹSӧOWڴi9w֭۷o'I);,88X ֣GSN)O<&?ԤIݿh;;3b-\P=$kά$UREW 8#^vq,dɒڻwrmEڵkP0;v,kk ,Xmۦ,Ym?wUݻwi;6l`ql=p&VdddVojUDDD7n^zzw+>|X+W*U1 E/; ygϪVZVYvm-_\ڵSLLLv۷k/=WkUl ՝],պuk >>>>Zl\]y=#77޶m}R{Fe5?QƌU\=}T]tQTT󻹹r2 Zn*VV`ACO8v֩S8:~d4iHO>Qn:e͚&1R^v֡Cyh"9RFqEIO?TAAA/p4,gΜٳѶiӦGu{n={V={9rh>`lSjf͚Zzrm>T*Uty)88XYdU);&NoTB-]T111SmVϟ$կ_p*ŋu]zUTP!URE 0kݻkɒ% TF-[*M4qڏ?={T^=rH(z`0hݺu:u8...jԨ&LU4j޽}TfM.]ZO߿_~ӦMYo P0{Ծ}{ouww_A4whhZn]vY^uRHDppj׮mRK4x` 6Lmۦݻ[g2e}v ^)QH@dd7ogϚ=v&ҤIŋk̙ʐ!ٱZh!˗]t)S8z5ydy.]:Ϙ1—$ 4H޽I1J(~I7nTLL83G^8+QR?XK/ܩ@&L5iҤدկ_?_zbbbt1} 2U@-^XoҥKզM+WN^^^ʕ+*W~~S@@6mj 8G^8+˸L0a&N(I{%sVWCՌ3$sڵkU~}}o߾^z%rjȐ!o$W0 ϮKvz'cFQ6mZmڴ)ނ$̙S?&NoQ_l+:d/'C ڲeԩ|II>l٢u&cVwJ>#ծ];1...7n;C /gX8+QRǿto /)y.\[ 7rHٳ{ _@2s a ^@ @ @j/d>s[ `PLL$z3۹sg _@2r ca^@27n\/B @/dΜ9oF4| 0@E$eΜy--|EGGkŊ86yXXl=ٸq_~eʽ1`OB`0h/ϟϟ+M4 jΝ;Kw83fPttO.777]rE|l٢;w*>lyXXlz Y|G/cΐ!nݪ~;?[2M/^TB|x _THɓGRddҤIիWe˖VWUҠ$W ^3gUjd r2*Yܹ`>}hVK/|^pWI;; p2ǏO%I}~7%vǗD 3WUb`GcǎɓcΒ%~W ^Rgx!sQUR%vוeUX1c/^X={`>gX8֫N/N(xH-̠A7xCTHHN8%J=5lP׮]:v=TJQ,ԏ>(zI`0hСq ^i %r2WztAUVMTT)޽[%K4:&((HuֵI+[lN 03WR7+xC   6L3f̈}o@rs2A...qߺuKΝ;gt\"E{n˗ϢoV3 ^q:^;$b/1V\riϞ=*U@ժUK1cbbԷo_ ^u^;$5sgT_Rھ}|}})x`Z^%vz6_kǎ8B&22R-[ԡC,#W\+00;6l/`&G^8֫N/  Yf>[o%cfׯ,X 2h֭z-Ν;W(P@{Q- ~^pWɋ^P8 I&i'OiӦVz״sxw|]rE7+::ZsUxxgz(z6`04x8;vP8 Q&Lx٣Gk\ݻjѢ֭[w8zq^ oXEkٱ^*W@\B_WDD̙3_Uj,q-;:wlٲ}z֬YO*M4+*X_\|YժUݻwg;`>G_8֫`!AiΜ9(xHLppW .HgG`Ǐǎ/` G_8֫; Wڹs'/)yZj .H"ڼy>|7naÆF򎯂 m+*e`0>x;wRJɘ /dÕ9sfըQC5k&Iʔ)6mڔ䅯>s ^aXR.75wgڱc/)31 TڴiEDDEھ}ѱ80} D 3WR7֫^(xHM테т$KN?4ibѣGjРgQSNQlԋ* GHx!Wڴivx:|䉚6mjQ111;v$ ^%XW0 8p͛EbŊɘ ϫB p IDAT @jzz HC @/d?A)$$ĪyҥKM6ŻѣG5yW%?k͚538z^.a04`͟??vڥ2e$cf#1 ֭-[J*iǎjNKw|iܹܹreUr caJ}A @j/d&Me˖I9w}n;c8p@+WѣhrWUN/&BfժUС^}}a_͛7׎;MV5kThh;&IU\9b+*KLLz%K> r2V:uS˗Ν;-[6ܺuK VDDI/^ݻ[p6^pW_LLzR+GHB&((HM4%IǏGʕK:t0o~(xfr `J(z_kҥ(xHLhh4iwJjԩS}z-M>ݪ8q `r *x.]:3LLLڴi3gΨxڲe"##u1-Z[jwTTb b-֨K"1D{KJWc^(*-5(bIٝ.lly>9-3lg&puu)S&[VhQر...zAdk^` Ȧ) ^Dd1laB#F@׮];j֬cǎ-rvv\rXr|988`ӦM(S^m"[WDdXB Bf~zi/"2Wyɓ'hѢݻvz OOOۮS.^B ˗زe ɓ'cҤIf^`> &i =UȈㄌ*c_B)R^wޅ/d;c""Kze!BA^ŋgEDf2ٕ*U*ץ۴i8 ǫWKWPXR^Y/^DDDDdS2?SVxq9rNhMպuk|RfΜ)ܢE 9HdSXR^Y7^DDDDd3x%Lr Ο?-Zٳg9`H,8eÇzDy^DDDDd  6H24RJ(;kQ(p9($$$ ##e˖76mSm_ؾ};jժ]3gbƌȜi߾=߯#1^Iݻw{F+<<ڵBÇQHIdnXlC/""""z ȒXݻw-[HҤzLj#k۹_...ѿ,X׮] pIB ҥK(Sn/FYs2%KoŜ9s0i$x~}͛g`EDDDDVYkIKKԩSpBtfϞ.cs r㣏>\"[dT_#%%|e .xxx9c-Y-B?Yky5ksx@TT={")))cK*'NF:쌍72"Ғ+SIMMxɓ1k,Y9<:tׯ_Fl^DDDDd27JJ(#G0""c2hܸ1nݺ]6??? 22ك\۫]6qqqq֭?kΝ;ѦM^zeJǏǂ 9s&LbpϟG6m >D% nR^&^DDDDdu4^GEʕpdDDYLFFڷoÇsرcao}љ]xYVR'N@…sԩ;w.Jcjժ 6jժڿ0"fʔ?VZiK׻|XbmY+ЋBi&i/"2WH=[W#G 888 ڵkɓ'# @cعs'n݊'*jsD"KzEY1"""""P(,Ld\\\qF#>Rݻwzejl|խ[zRK?QYte^\̍-M-[W6mxUɓ1k,sqq6*V,c$2'WC/""""8۷D'/"2W6! sIKw .>^2G}5k4^5u*3lc$2W C/""""( Ȓ̛7o_|)XU ))I>l-+seR 6u|DyrЋ,Fzz:2ZlY^DdluBt*̙ :t(TEFF>-ze2eԫW~)f̘-[HHH{|J3gDttmr~X"""""3}bҶ'GFD-OȔ+W'N_räIdi  P+Mdl^;P(?3/2 +ҖB 4IKKC>}_Ix 999aƌ8qʱQQQ(S Jv777=zT{\t kݻIdk^Y 6h Kɾ|+WQ`h(ezE=li ;̎NǣYf*>&MU{yyI&INNF6mj#}Gdk^Y3lڴI㒃'OƬYo޼'O2777ٳ銡?GFD5OȌ5 nҸĉ}ʶoF ԩ6l`И""";udP[Dƚ땵ӧ֬YNիW/.]=>cxyy4www۷Z2"m^>xO/""""2;iiiݻ7#mFXX/"2;|||b ѪU+$$$}ؿn/FBd?q8::GڵkmذAc Ϟ=CuٳgѰaC ȴXPҋBZZzꅽ{Jۼq1+W.FDN!`gg'=OHH~^l5j @ضm5j|u~g}FDDuԩL뫌!s\6lÑ???lڴWKd^Y[nr労mٳ'6lWWW@WFt OɁWzQcEDZ'dJ%tI&q@3xA0Jv{,X@˔)mۢt(Rw^\v PdIbŊ:XkѨ[.?~vnݺ\튯9s`ҤIZ+>>-Zݻwxɰ^\zQRx+Waaa XҥK1bxxxhܸ1mۆ}ʲի1|p{f͚ؽ{7} ҂5+k7xÇZU˗s5j9Db2o7֮]gHHH@ٲeQ^= 4ڵQЋLZZz`i/"2W!_ܿh߾=;whDEʕq-Z.XQZ5uۗL撆퓶1""se2BtAkĉhڴGFDze 6m!C`С? "##agg'{_x)^}b۶ms{{{m 63]իW1Oz%"""",-LȬ^Z x 6 ϟG|82뒒!D/#җ-+Kh"ٳCJ˕+g x{/9_ ȔXۥK}vy5aT^]帤$,YfBRRRvѱc< t[H)))ԩJ̒-L( ̜9P^=^QlYݼyǏ70رcѪUM/[W޽{6m@T/ĪUPreٳgF?} ȔXߒ%K{ 6mNxok? .vj ;f! ȨRRRХK>|XږxyyyȈ 3g`޽9s&D 0vxôhCΝ!@jpAGzehٲ%?+VƍqIDGGh@Uw^dJWOE˗(]4.^"Ez^bb"zPbC^DDDDd4 ȒpB ׫ ;v4,Ǔ'OPfM|RP^YKbĈjի1x`|򈈈L|) 888=zooou0`IKKC߾}{nM|qyC""""2d^Dd18!t?w) |L<2ˠT*g^-ZΝ;y42W!""?JvI1Riܺu <L|=|e˖=../_>[m6tM~S/uЋd.X"/"2KQk׮Mׯ_m۶ٖ"`ܹ Sh޼9]fQ5` JC AbbV_x֭3XRSS1h !xI^QF!** ;wFHH<<<>˧s{NNN_ B>5aEDDDD 9"mX"='dryŗ+)) ;vdſӧxGիY+o|Ep^dRWm޽سg͛7޽;n޼ ͛7zoumTV 6mһxO/""""Mrr2:wGJxℌrǗ;ۇ?#3/[,XBz<2tWnݺpv튱cǢ\ry&~/e˖8x d/3"`2oɨZ*>|C\\>}ŋGzz:=̙za߾}ߐUŋ@^DDDD$ MWXXJ,##"Κ'dӱc8q(VڵkΝ;\M&BbÆ jϟ!!!hܸT1ժUѣ 0'CK.Etts .CN:*YkWӧ_|]ٳgcԩ6l/_n!Gؼy_l%Ɲ;wвeKDEE_@:t 40Xk"U ^Y-[O>ԯ_͛7j*( 7nA}yu͛7UkbEDZ'dbbbЬY3/ B޽WXv-s<ܖG۶mqỶ+nXh}B|7M<*"^t IDATY+&O3g`ԨQXlv'''lٲCR4oWWWtIe1zΒ^#kIMMEqm@ձg %%h۶cpfhB~^1Xk"F ݗ|ZJJRUDzeY.]={`ٰw ۶m|YFƍ,7$""""d^ǎe^%JȻV?={ETT--Z`(]t^Hv7 'OָsرcKe˖ԡze*AAAׯ,QN}ڷo/;Q_:u¦M?~iìފDrbr/uk$''v/HDDDD6%)) ;vT j֬HW~p<ӦMS{M9D"„̉'hӦ|+h ݋^z!--M6.]K~~~ h۶-Μ9c⑑1B2w v͛KW˖-~z>}ºu0x`xzzVpp04i'Oh1c`aEzeN9m4QdIqѤI:yHLLB$ڵk% 3xbѧOQn]QH1gϞa l^[% 3,ׯ/vZǦUV2ea͚5EBB6l4}pttosZѣG (O^O|A___[շo_# 1g)J*#4cEDDDDdle3gtؿt% dpTzEʕ1tP}_3fDNٍ7_{Z2MW… z}aoo/ &|-}HcZ!xQ`2_GŊ3Y:tqqqZ[bŊ"**ȿzx|hU/ &J,)֭[p'd&NvB BTf;~RPls V*Ν+>Si[۶mMRΝ;wT&|}}ʕ+T~A*Tze ^w;v^~WϞ=u8>gggq֭\9y$/WkYzojԨ!F!ƌ#>QV-é*UkկW^^B0"""",^111y=4Rpp}gy=" cǎihx IIIK?6=z$˧$… 2NrrZ;OT{׫WĸqT`el^|nݺ&b ,UO< ѣAc}h۶ƿ~x~rr]6/29+u.ŋ`ݾ}[L4Ixxxdɒ޽{Z[eB؃@||<>cKjժÇpy82둔+WbܹqQ ~0JD`ʔ)={ hٲeJ>7n͛7k֬4hm{QHm6l@TT\\\W_iL29rVc+V ձ?3f={ƍ/_>,XÇ8mΝ3ʘI^LׯQZ5߸w_ܹsuԑźut#88B@R o>L۷#::Z۷oŋ`h i|EGGk׮HJJBҥqitQ~~~3g߿ѣGAcO>\Э[7̟?_+",, %KS^nDDDDxݺuK$e'Xzu'2y s!~(^(QEDD___wPD qΝlU^]]tɵgϞ GG ȫ )\\\J{hdzY&@bB>o߾SNjUT~ʕ:ѣG2nR)zowʕ9?ɰ^Ç /^ܰ0Qt?uMJ(Sʹ*U2+21""""qqqqaÆ (>>^TPA\ZJ־21˜~왬gn4ɫ 5kGGG}vxÇxzjtR5;`?߃+K.W=M2E=3F6D|YN /==]dddhe-[S 4ڵÕ$%%:udСl}ʼݽ{WmƍӫѸq͛7kVr6ٴxkgΜծ]K,00wUoųgd+%%E9##6lLk֬A?6QNjɝ9Ocs j ϟ?'f͚믿ƍ7p1,_v۷/ .m oÓ'O>ɋKctըQ~< iiiZ pٖvssCPP<<7d_6.oooO*۞>}S(G$7+'dooʕ+V|sN|jغuk0""""Ih۶-2 渿XbUzu瑑Xpl@ѢE%11Qֶ 9s* UTGrr2\"=vZ!pYxzzt ˗WhQh|޼yq_|ƍ>۷ԩSqq9.\ƍ 55Us˔)|̙]CUU*^O +åcҤI(\0+ٳg/K.!22Πl2:UN_l@׮]:zٜٳҶЫ H ͚5Syf͚ٶ͜9ϟKDEE.:y9!w/OePQQQBH7lؐMvޞry7n0vuoVXj.\(}O?xصkW3FNdd$V\y*ϯ\y4駟l+Plip ]tO?W^!::SLA׮]e n޼)پt8o-\0s6mڔc? ӪU"V^VT\Y(JYٷo_{-KۙN8idmzY1M׫Wzhg ѫW/#GLprrZ}%"tQNiB)S|~Me'k8{ptt*zիWe闲k߾}߹l_@ӧ UOIIReNʒxϟ!V%DRRRgddڵkkvvvbΜ9zqd' |j8wl}sݵkΩSd5ee\xe}x{{˗~e.+@̝;WsF֭[G ł ֭[O$c2_|)ѣG*V/6x\%KD6m n+1""""" ,BM4qRE+%%Eߋ!;;;Փ- 9A bذaFkDD8w]ׯE``С(Z...aÆok7oVJ*%o#Qxqf9z״ >ʕ+'VXq)fmIm-ZT$$$zR'NzE}cƌ믿eʔWPA<~ؠA+u}QN_X&N}Ϟ=+a0' īWDݺuU>6nܘdZM|)J1}t'2NNNbFzdmBwxTV^ĤIĺuDxx,KӮYFyvlBH"Kooox 9QFiGEEGx$^}wïUV~mVj履~ WWW@|z_I-+T*G}$J*|ĈbŊXg+JѲeK@ԨQhK4C/""""+"ZO| !ĕ+WDÆ uV8{^5*k1$ʜl(Zhڴ2d;wصkyf~oV]t%ۄǏM<ݶm[G.X s .,&N(>| i&i"핸Ν3ůXk2w h]n_^ZܹsN;{ݻ<^/>{dtWmժU*>ׁ}5G?Zy5 e%^DDDDu>|.Oծ];ѽ{weKTcǎ 777m;99͛?4$+k7V^WtA3F͛7?CL4IZNIa^*J.;tO uB ݻwgkwذaZogg'|}}E^D߾}EN >FgT^z%\u֕~"44To^ ty?CB={ژ={ίĉ?9֪UK y V+ ~>?_IMMGΙ2e/l !"EGGuָr労q Apd/_bȑ9s&ʗ/HOOǢE0i$( 7o^fdd͛|2ooo4jzW IDAT"uLٳgK-[7m4̜9S>_~%JFn݊ݻ}kƍptt1bIs׮]Xx1bbb4SN~6RcѢE?bp[?RL%,, ֭òemױpBlܸ:]|yЍ7PF ( *T}4*UO>U{%K z;t+9y6mV{Q\9|r>}y3g4+#2+cǎ\>W|m޼{ 4^戡be>v-M,k|ݹsG*UJ\zU~__dluBF%K޽{ ???0^˺tڵks=^PVZxe:~ GGG1a.?kZׯs]YӣB b9} jѦMƪo={L5uT̚5K>GGGT\Yk֬slѣGz*\˗/̙3x𡴟2޽{ѥK̙31e[j o%J+W;w ""BzгgOAcGPP5j ,,̠ϧ)))(_<>} [n_+VĔ)SЯ_?888H۟={___$%%@xNɈ 4hlق~=z@jj*vڅ@=G{|esv܉K.ݻx9 .bŊN:h߾=6m '''C_yԍlWx5mڔWx7oވRJ\a_K.1u[м,~YTC &ӧ+Whu^NKf>˧['O^28qܽ{VxB.]Z+'22Rl޼Y㣣EZ|C/""""3)~2GIl޼Y+SnWnDRRtR-~ M(uB&...-M>]vr u׳gDɒ%xOzO~7u W.ǐIOZL-keoo/n޼)k .ޣ}b̙p:_UT[lf͒M>|(ǰIG\L)k@GRR(^ *v؄/HWȺXELL쯁W$ʖ-u NiK~ ̔Yf ذa4JŊe2|`E`2^...m͘1C6H>*U '''lxerttϚ)xFY"pR9{+%%E,YDx{{~e>&L zw-[=zQ@s_^z뒟x䉡/f؃΋/ЪU+\vM֬Y3߿^rq_DDŒoݺu zln:888e,dLٳgK-[T{ァoXbZ9sLL2E |'رcG}=z4NhҚ+S B~=..}d﫯jl`` bbb#Fݻ?s_K,AttA%U8pʿ7n1B(PBÆ u!!!UwG}Oܶ-bEDDDdf?VZ6^^tAAAFnݺcx1„ݻwq!̘1CcȐ!:MfiۥKЮ];ڵ+uꏴӠA7nĖ-[x@BB3Gӱze LG/z˗/ҧ;Əx5͛NNN4hnܸ͛7zZ4^"C^=l[p|FxB Ç5_nBV_7oD&Md//5#"""y}͛sIC 7dQ︤!.P(TϚ5Kz֭BT*ŋEƍȑ#ׯ/Qzu:dkE_ӧOӦMS=z4{*&M$J*eВ^*=~xC^iZ땩[0G֭ٳg ;11Q+VLZT*Ş={|՘ 3>Xٳgֈ-[,ix_b߾}"==ٻ(m. ҤXbcbA-F5j~1jbѠhL4jƆX5*"X( /ceβpkKvfΜqq3gիW/7o\~X2^f"!!kԨ˂UVMx=c˘ӧxA(2r;uǏ^ߘݝrQ)Be?<ժUXme2msJ*x13ZJhoɒ%z > ^hj̙3ܹsgE ze^d2/^\e;ʡ)Rxyyq||}D LixVZaJC r!$S FEE̘Sfdd()  ;;:ttB'N "eI$WT9lllhǎԣGJHH ???ɳ.22wP;Rک*{ה9իhVU~46;F7vŋjcܸq³V^Z-ZЁٳԹsgu}ʕW?ze~R)yyy\߹sg4TET'NP2eDE2uPة+--] R~}~32pذa񕝝k׮~M/0|r\\+VL܎?׭[}3f ِ!C4^믿* vssSAw[N}5djLP ׫$˹m۶LD'_~ .ӧ?u痿??^B#F0\=z`T&M2}m^=zeƬIII S\+[pW^LHYծ];NOO7u@sb_r7oŊ㤤$u۷oGFUd:uwޞ;5j`GGG4jRRRH222^{~{ T .ԹΞ9sF! Pv\GAW---gϞͯ_γ.==ׯ_իW+СGDDhݗa1o߾ `+++&"H$uVQPʼ bccgv!B/yBB&E /&"6mGFFSd6lؠw^xBhhK$?B \J<ﱲgiӦj4h/_֪/ׯE ZYYիW>e_̕L&{rƍ ڵkgΜX .8p>|8 yd\D j1c"B/xYa۷o3g"b32׬Y DWd 0 lZCuKxxQ~~~ Sk֭k.Gy۶m,նyMgiZӓZ6M6FPꕥq]~v޼y¶}dz0eh߾x]x?ܹm+99SSS0`/ G,ÇyŊܫW/c___8p XoܸUb_ׯ__lTa y r*% qF^ ĉ`n͛\|y([j֬ϟ?+ ʒEGG44JNNf777&k׮tze9BCCUr|EGG+wy]|Yx+/ '?^ӧ]u1!777̏(D0 r93F޽{MMqx(ɸUVJߣ!CQ͛ӧD;6+;ްaƻk-u;w9sL}JJ^L_Zjyꌳ3CСCLD\t<{Πs,lzQ\\0uVұcG^`ҤI{VlY^x1;w\5J+wڥ8ASeWBd "E0OMѺ}MСC˜m۶֗-[T* ںu+)SFaM2^J: ̬YhFr;w\.71ݻGVNJ'N`ڻw/xB$ /4rH322[ntĉ<233iȐ!`RUXʖ-r}Rn$Hhȑ#JOOOF>r)11Qi^x{{ƍ)22ƏOvvvz*۷/zd_PJJ uЁvMŋga^۠A?T~[HHy{{իiŊԠA:~~Ȑ!deeفԩ@A Wq/ r 8p徒S;#GUxϞ=+Qa+O<7ݻw`ݺu7j(YveFv` 5k0ŋ!x֬Y\hQرcZKSED666gQl߾]!⠠ j>}`@^JNN`.S[P^,Ⴈ޽{1mۦԬ666||WA  3Çr45a@WquѨQH.4a6mɉ^|w_e)wN:ENDDTH}мyݻw:M)SL&ӸD"UVѨQTnFDDdccC;v=zܷ?7nз~KǏsu҅ǎkhf)wrOiԨQvM63.\rи8>}z~([ڴiu=dr:!?~hipЌ^eo>^hX>M2%ߏs6m{yy+7i҄.]ʩޗbbb^,?֯_14bf`"bz d^DĥJ2I&1D"ᐐy)_c5h qu^"ҥ ;88xe4_C Q|effWD fz-+ˑ^n]#:%___zZ@@޽SZF)|UZxS>ʕ+GL};w׸8;w(]?yd RM6tY^ ͛7yޞ8@mڴ8/y3ԸqcQL]zer.^+V=xJ,)qH[l ҥKDkt;K[FU1B:////vLaP[z@… BUBڱcy^|I'OTѣG~F9pVTɠVPΜ9C͛7W~͚5 $[v-/>|XiEDN]t'Nr͛ÇNoq^h rY[[S͚5^KIII&z;>|(j^P֭)&&FxW^ mR:u>mڴISRR`kݺmb25jԠ:uPfҥKԷo_a֭[Ӊ'TR-"]'!9tEg777:zhgQxx} FݻwGeT=zmөk׮Muؼys #;;;۸ѣGx9+m^Y ym˖-"1%SuU>0bŊQxx88q><===iƍϗ/_0.h߾}T\<٣r?[[[ڻw//̜9SeVZѲe˔sqqpjذ(ER4A*W_ѱc n?%%_""*Qzjٲ/EL=hтn߾U{_Ν;Ӂ $6 D*wЁ+tυ?_pAN*QLڳgY[[+~eюiժUDDTjU $__tA1%J/_/czUqF/+RЀtn7>>իGDDD'O6?^"___a:"^ٳ4m4w}8::җ_~Is%777{ 5nܘ5nDM4%J==xnܸA YYYѾ}sڝ> 3j(Zf խ[]tzяըQ޽+ܱcG:|hz /_^b())/.1@ 6lm۶Mz1K. Be\^ :\.UԮ];:~m+ R222ϏΝ;GD.2eʈgB/\T^۶mS9 ЬY4NVT)ڰauQv߿OC !Hhʕ4f 2ӧL2TjUx UZ޽{GDDw咽={jߟ9ږ-[zcC?S9==]3Ә1chJӁM6:[jܸ1LԨQ#C J^f۷oM6њ5k0+󖖖FG7o5ժUNۃnݺj^:OԥKm=~zAW^^4hmذA``fﳻ;Ӈ߿oꮁ'NTx4-ּf7o\c|aĿ(l;c|ĉ|;ɓ'ޞ9&&Ơ&O,G:uX.kg̙y~ҥK%k m.N&СCUX>u:uE; ӱcG&"vssM6;&ze޽{sŋ+Vl e+++5jīVDyߋp%[n^!D"˗k},^x1;88|J*qDD1LNEDܬY30/^p>vҸו.]>|hPr?~\h~YXi;;w"2"S+Ntt4(Wݻw甔Sw ߠ^ԩ{W˖-9))I6WXwŋW+믿o H^*11;WWWyvvv_111~1^zrw 6T;͏?6uW|;vL^9KjѣGZRT- j0>^PEFFrٲe>1c{7l0~6AAA*\\\'?ޠ ,c"b[[[>tmr۷o9<<>fW^||} $+󕚚*\$HxʕJ;u+WNK.ƍAZ^[ .x ^P *Oe7n bUч>|X^ZhK+ggϞq`I&ZߙveЮ]zJΝ;^}]H|yNj3 4ҥ0 SwdPӅf޼yj}1רQC+ǵkxܹܽ{wd\2wܙgQ ƃ eLfkkw g},ZHЍd^VZ֯,RM4#˹o߾immͣGXmm߾͛7War%w˸L]@Ç3ч;`/^h yKJJ>GtA;MVZ_`$PDFFR6m(>>^x?͛7 {X|9M8ʗ/Oqqq:Mj۶-|2:DB˖- &U̚5ϟ/looOaaa/?uPzz?@>>>{˖-4p@"";w}222hgK$_>k׎ʖ-Kŋ8ڳg]tʔ)CN"OOOΥMҋ/ʊjԨAo߾*֖KW.S>}T...N 648hm۶t j۶-;v0 +O?7|C\BjDPٵbŊt)OE-%Sn޽{\L;,޽{XbzTz@Y;T-[Gd2]ކ oƌz;gU?|PVԼysu3{*mmmŋ}<˸L]@;Ϟ=UlM޽2u:uJx~wKHHEqժU5!Cpff@W!==L20MXbLDsiҤƟӾp-T-%K1cf|5mW>1u9rDxڷoq/H":hze9UI$yѢEݠޙӣGa5k_߫WK.S{ëWbJC#2ud2nѢ^uƆ |Ҷo޼Ç?W+++:Ŀ;?^6˖-SZo߮L&#0a޺uK &M$i CY5tP^… ɓLN:53g/0:SȜg󌯬,ްa(Q5k 3'TV899Ys.LBzeTQvmNMMU^&oƁrJ~}6A7of"RJqݺuu/9 wMa 8p 駜>aaaloorcǎZcӦM}U 9q{SfM~(&&& w{yyy ak׸W^.t"^n^x[r>v3vݻwÇowѻSLa"L|dz}G͔֩sV\|Y(ȑ#9X^`^Æ Ce޽Ea'''>{,337nܘ_s{gΜ-t=2/"Zj~J#Gd[[[ïF ?+WN;9Aw.\`;;;Xw|5J?g^z|H!!!yi„ Z33]tIɓ'vZq:JPn(##R)U\Yu}xٿt֍Onq@5S+PѣGԨQ#Zv-ry=m޼ᆪwމ/LFÇ,>|8& IDATى~ ^Yw n޼9]pY`N?~n̴f""j׮ e @W˲mݺUt9T߿mkחٵknZ|W |ǵSgٳgA܍s1c+VL;˖- $;;LJo߾̏?z펯L.@//<Z*yFӄ^z?r\+?^>3~hdHNN2eʨ 4/ggg:t(Ϙ1[h!ߋt2ܿӧOϟv܉' /[L}^~sŋ4xsA{gϞI&qff^2jUjU!lۿ|988[ =-P f͚xh ::Z-Z͛7CiWrr2Khwb:@Wƍx stt4O2Expl"( F/v&Mh0buN>^`,5Ǐ7xz m:6lP|U,]Ԡ#S'''u Rh&˅; ?]Ɲ;wV[?۷opG3sBB0p{)^8תUkԨ666 ٹsN@ze-Z_9uHGuq?.\PyCfdܴiSsssS|yatyf^L= cH{#G'OG||^ܼ,ްapdɒWi&+X; ֹ?S+ΥKՕܹ۷oW;u쓒~~~*f͚+P!_^H.ZhZ1a^z5;%\.ZjiCW *24 c&".R/Z߽{!!!,JU~94iׯ/7dDvvv#G>}^ᗝw~}B;s1ܶouQ%K~aֻwo&, }ꢦjժ+3e}Ndz[|ڀJ=իp@Y蝙_|} ʼd2g%ݛ% iF޺uK=<==իWB/0[׮]CU@s=رcy֭_^m5yd}^/e@ɓ'x ;88Dʦ#[`(m 2_^K.rJ *U(A@fe{X933SvmVVV*$M8Qf͚ܷo_LAə2PyڌU107ol1$z x;wN[ ׯ_k|+WD9VVV{zz L *ltٳ':t(Ϻkr"ETֲƍszzAP^͛ YZh/^й=1JJJ^ 0 sU TB+[R)Ӈ###=zB}u ._a!WFFK㸻+YsWŬY41B mרQCzN8VVV:}z1o۶M<(\l۷/Q SxM8-KWL*U׵kpWUa}6 S~ݻw?sů >NNNLN;ooon֬׮][PӐK.+zu҅E>k`.|ʒ|+ʊo޼)1NhB ԫ5g/(ʊ7oެq4_&L~B/0+W PS,ڵks]!*^^SVi a%K5Eadz^ݻwyȑ 2) ۷ogkkkJ77o֧۹uF cccWZŅ;v4A\,ь35koߊrܦM )SF6tz޾}*T:Zp~Zc! Lʕ+'7yd='NL&S&%%9ƍzqTT^|9::rxk : |s9sf -ZPΝ;>ɓ\H*\ڷo) rdeezeY?Ύleeŝ;w_~DcΝE]0`fE8ݠ^ի.zj9;; t>֑#G>S KtVX! XrLMWdJMM`.WNWo߾>˗D[li_`}JDD^^^t *S}Gj@p9^$ˉtRA3gΤ&MUX*WLTzu߅UH$ԴiSx1`@0-Zеk(00PٳgCxx8YYYx͝;f͚t]vv6ߟve10WC@@޽lmm?py{{ɓ'5{:txUPA9P̏T*%///;w,zEDԩS'ڳgʚk:u/ B/0sΑB5e^ѣGŸ_x>IIIxb-Z… ŋݽ{hԣGW8g^` g}F7n{Ul=zN:+nܸADBwܹsiJ!2-+ˑMoߦӱc… 3|=}h*xk׎oNDرt;!^RJ\e )ʁ tf`'''9LbnfϞ-Vyذalggӳ} ۷^`TxƄq_y~}||Lݽ#w=y(md2ݻ7eFP_jj*]u癶988++Kv5=+͛ѣm6ꫯC%F[@2wm۶͘޶mzyyq||ю B/0^ˮ]` zjnٲAˎ;ah0 ^xsJNDdnR(T_r!2W...:}}iu m/uK͚5F@2w2K(U0f D#af&EDD? M:,Yb^!222t TVR*VHO퐐6lVVVqF߿(ǃPӧOo߾t9Сmٲ/vR^4N [boɓ'};vk׎iܸq?|IhҥcxbgLi@CUp-\fΜiPգ!CPvjժLvoVnݢڵke0 Mf8rHEi;++J,pǬi֭ԧOQ ʼEGGS6m(..>L-[$gggJHHzVmUR8@^^^j&j߾=թS7oN;vEjRzB :tprvvpjԨmذ*z𕒒BRٳ'ݻxLz8gϞ3iL-ɻwQF:O#J[n?l;::˔)#v|<3(0DӦMS2M~~~3T}Lu(>+^:[YYo߾ͳyʔ)󓛛ߺuK5MuئMNJJ2i(ze9BCCՕ/^Kxuhvuu3gYw ^ 3g *={ժU*첷篾JgF>|XwٲeF>(0 Uhhhz\xqnݺ1qǎy֬YydܱcG&"P<'Lj?K-[N߾})--~'51cU^ShL/0‡Y(={FDTlYjԨf:uSN2P,X<תUTuy{{Q`` mܸfϞMS5P``N*U ^L򢘘jٲ%>}Z6޼yC{Ǐ&((HeM'ORҥu':WX:|0uԉJ.MO>U233UB9{,SZZ/:z(=z"""(%%ҨhѢ駟P׮]>pXl_ҥK%$$A : Ȁ%bfTJIIUR DժUW^Y駟ÇK~d2SNъ+믿֫ Jcǎ%kkkڲe QժU֭[J.P,ßIC%"-[Pj'++ wxT;!{GAJP@~"x$(Cӣ ( M"R DE@z( ded$3ILk3$3뙽s/_>j@ٺ"h?CpXpñc̋/hrֹ"""L.]̱co߾Fҥ_sL 8p Xb)r%̓O>i-[f\n?mڴTzv$+8 5GMu%44̟?듕QG-N;vj$o73p@J_+Wvy5k֘ Cz\}P2כVZ.TPtl޼٭=W^}hzKhxSy=)y%wf&!!繾;bo1!@ozmܪ5k46lp9Vddd۶i޽;</^4mڴqڮk׮>ޝ QǕ+W@tA;d7UThw_1& :Ԅ`3uT<dmԫ믿2O<۟7on>ӧOOk2ӧMBc M/` 9szj֬Mu_Ǐ7qqqng[nɟ?d:ug Uz\CBB;c_4ޙ3gLJ6G'4 ,0Ν3$?ȑ#ft)9ӧ / W%&&k>s^V5+fLٲe$^p*Zvˣӻ(P|g鎟^3_x4jqR%J^#^ݱ~o̜9ѸtٱcyL*UPTZ5sٿLɒ%Ͱa$%%':oJ2Ok SD kMdd4+W6AAAu .8~Sxq'r̙"_|sIJJ2< /PϪU&O쳱] ?k|ݸWעEK.mZje5jhnwܹsK'ߐ^RJf„ fԨQ)|ӿ z5]n[v- 5{ldiӦMͥK|%KL@TfG|=zԩqaڷooNj~Ws1ᅴ)Sڵk;WJ- Ä 8*,,|)0 IDAT=xy]gϞ)Lrn|vd꣏>&U`3gӿ?OgϞ_ݻk|W?;/Z;wnՖ-[ZG|/^<ŗs_B+p;vΝH2 4pZj"-?Lwr]v)={y衇'Oӯ_?CÄ IRRcٟPߦ.[t]wǏɓE]G?UZzӿ]PP>z.wwQ2;*Qٿ={ qf͚W_O_aaaf͚5>x@hzt]4qcw,c…fӦM~_4Lܹ=+[lI3fʕ+e˖nS^=3asY_}o= ɬZ^jz)+U涇r,ub 7T7/SسgO9uꔹ;-\b"##9++{?qTcPPPG|}w&44HKD;vf͚}QHH>}Okٰa/znw9s[l߾ݱ7GxGzV\-[*..q5`SeMpyeTF Ƿz6nhy{W4c ܽ{~T۵vZ͛7O=Os!{d[lq|ڵti5nX{ŋ>*"""|rrze+::ZdI 6UV~WJr͛PB)nOHHPΝΝ$+V̫#8[Wׯ_O111jԨ.]^1cMm\B2+WUVN A6ʚΟ?SNzѣGzox>}ڣ% թS'͛W۷Wxx׹H3|7&gΜNKX 4XYʱc;c~#GḺt$$$x-[8ѤI>M6x;w>!)),Y4iZ?n6l^yǘ毿rSNի[^2|U^XCvXƌ3+VL\֭͹s<Xb1-ez彸83ydSf4Bxxy_ݺuOJJr۷"EX^0<88ٳǧW_uGÆ }:>&d۷o76{nN;)mcq#<3oK>Ku2]7ovٳ 8]nvn:\| 1oS=X?ܣzSܾg;wN ҪUTD ƽƍ+I*Yqo;wN6mryŋh߾}}Scƌh̛jJ?xKk׮Z~J*e9wΜ9G}T;wtVxq :T111ѣGuEm߾]/rʕK,ѯQ2eʨI&)nߺu_O ,PXXGlʕy/Lx:tݱ]:^:uK.ٖˎozɓ'W'LW^1Ԁ3 _KHH0:tpjݺuGSƚ-[ '|>,\дnڄ{dD…ڵkFdFHJJ2_|T[Gxlذ^yܹsVZ/88ؼ[?Zjiւ?<_~e5m$%%Y}իNuǀB l-\-[֜:uHo߾~ 53fjI{QboLג?4I5rHOLL4Zu|rꫩYvms K.Zi[^yo߾}{==EC GiӦ~m|ݰuVӧOӨQ#SlYd (`*UdZnm>cs H;/^4<˗/YrG?>MRRW^c>&>>q1_~q"""LllqB ld4Ne䃏??ޔ,Y2 {ҷ9&f͚Θ?+ߩ3{lHLL4QQQ.kjb,:w)^x\rYɓ1F5,eCW=z sy5m۶5|Yz_̆ ̒%Kȑ#MTT)RbfΜ~ndƎk1f~k|%&&Zw\b~aWpauVǹpႹ]֝R_~%#%z]vy4q$]Zd$^d^ݱ!C8^H8Yti_nkTHeRĄ o*sZҥKxx\p NsSNNX]~|G&$$ir6zk׮.]8^9s7|Ӝ>}:m_n-Zd6mꘔ NsO?ԩu_'O47h;W޹vѡ 6r)MZ[/9s47'OLsXK/}ٿl@F@6|C[W^u:o;R Æ K$SD 駟:iذdrՇ=VLF{勣Sunݺ^9ttkuPPyG͈#?~s1vZ˧橧"%n>j%,jG5.?ݗ$SNS@sY +$%%g}͛'{W^<^bbi۶m%SO=e&Ol~W~sQb ӷo_> d^dq˖-Kz펕̝;(P\vH.<{5'N4Ǐw>11,_y睎 onń aڴiNGPEEEl *8>^ٽ{wj5k/f+7-[>|1=ܗ'Io߾^eENRRٳ_DzGy?ޫ1\b4h<; dY˗/WTT^*I ȑ#էOe= .t|})4448qmۦnݺtҪP|A5lPe˖?={(88XcǎO<ɑ 0@v\ϕ+.] ژ nz畔nsޤI?~  Ǫ^/_Ʉd+:tbcc%It42_5nԞ'Ojǎ>zc^u92}s~n~/q|^?\C Qxx1[oiܹ :Qhz-[^4c޽:zlLRbbt[ǎguU9rp{P ڱc6mȦ?Ç_~.\gU޼y}2$mV;wT.]<:zPB8q/^,WrUM:UTdI6'Jm۶9~>t/^yF#nXd>cϒz"%DGG+&&F5hۊ+jŊ<d v|oҥNܼy;ݻݑ6mdSP!s ٿ2d^CBBL {g=Y瘀?z. z_7Ιl۷ה,Y2ZÇ .-GvF/:8izDVk֬֨Wqu/WP`o (`$%Kx=6nh^x7o^'w&22̜93ӞpGϺgv˖-S6m5jTlVr1'N)IJKÇkҤI)sw(::ZѺtݫC)w*Qʕ+R2!#n5h IJh>|X֭$uE+V4;*TCjС:|bbb_)_|*RjժE#o}k֬i_7 0@o={ŋׯ}%IaaaV^yˤsͮ_zJgVTT-[qKvڪ]&N{*&&FW\Q|TD ժU˯2J1xo…zꩧtuI42R5d5kj˖-6&r֬Y3-[L!!!ڽ{#L/f̘{έZ,-۷ٳU`AܹSԦM{nJLLtL$vmZ|A̙}?VHHO)SFeʔQڵUV-=>} FG(#l= Ă LhhR+G;Vѷo_e!S]J7/اO,>}Ӓ{y'LZ]H:;wcbŊEB }sι8Stifk奁WQ^=k_tDxbir̙cDDݻKaj9Ps뒆7xoӦ?ᗥ{ؾ\r&wnՕ˛cǺݻMHHM\\(4pO3fݱ[?`J2'O;ŋMXX`'&d_|UZ5qFSP!5~t'ӻ.\،1\z5;F޽P'22'c? >q=qfӦMn|z7|5ouϟߜ9sXv2=z0yo1""|WnƗz@ZWtRnMbb̛7'e˖;'Zjŋz뭷$Ӻuk^'  @͛7/Ekرvʖ/^GPPٺu ȶtR bBvرT'nuaKGs)S1b9y>]fڴixܥK|5M05n׮[G*ܐdkӤI䳿Ðl22?>IP3tP/6o>׏?u>Kٳgwm$'c@@?:tׯK4f IDATNpoÇ\r)noٲ/^\X`ڶm+I;v,+PTոqc>}: 0@t\?r6lHʕ+zK 6T"EtI=zT?,Y;vXʗ#GYrȡUViΝϞ= Z^e WwѣG+$$$Ξ=Yfi̘1ڽ{_͛W?Vco߮h}~ռys_իWK>}ϟ+!!!DŽjيrܶn:EFFҥKڵk_W:u?SGʕ+tR=zRիWOժUӕ+Wxb#7o27W_57˗/OEXvZӮ];}qiذaFOy!b޼yСc ;V=z9'|}O?> N=#I7n7;A pwׯիW+44}?^|F+Wx}uV/_m6}{9M6o?ׯ4i 1O?+xy|-)UR7{W5zhK:xy}JJJxUjƍ PW׬YTϱkر?,Y$A^VZ"EI&YjxIMCjϞ=z#G-R^~@ׄ Tn]۸qcYq^R.]hN/^̙3} z9mVfJ|7.\XÆ kƌںu{1ZVXA M/2shxqN;zϞ=` Llݺu`կ_߶A ҥKkҤIQ-O?v^ΨW˙3-Z뱂ԼysmذA~~aoذ{=y UnZd^VXUVVZ>ETT) 7 ;w~ ݻۜ nݺD>ѻwo}ᇎ7nHK؆zxh|_)&&F.\PrTF ^dB^ǏWnlN?~\w}Ο?cBBB4i$uyhʕ+>kw1!$&&F5Jջwo͠TUwY͘1C˖-Ν;ui\rUV-EEEf͚~I/:ZJ5˾Pׂ Ծ}TW9rh̙j߾}&^d2s3}Ժu4 ƒ%K4h IRկ_?2ȪUիW{d|>}dP*xzw:uzѢEmJz5iFfRhhhILLTǎ5k֬ Ld}4$nmxkԩ1'((HSLQ }ŋu_~iQڵSbb$i*]O du4ğ_|Qw}FӧOMBBf͚jժw4J*e˖)^=&d]UVMVroz\\:tu:"nA1FT^=5nDWC6m_($$$$&&SNZ`A9vZhG#&%7j֬YرAqڜ {nh<ȑ#իzHUTQbŔ+W.]pA֭[W_8z7ߨbŊ|Ƙ۷q~!7|࿿gcuQ۶mS2eo>;rDK.U%IaaaڴiWns*duԫgj߾㋯j)kuEǏ?e˖;.hz`iӦK.JJJD+UΝh"N:ZhJ, `BU]vʙ3֭[%K(11Q ʕ+ugPz}c||Tn]ǗƎ_~'c^e_4$qƊT\\kOʕ+ ^dS^pjxM:U;v92̙3̙3>788X={￯0  5jȭ%ho?~}7Seoԫ#))I/ .>׶mԺuk:tHԳgO9GIרWX`ڷoׯ{ݝwީ5k֨TR~J>^d ^̙3:tƏxǫUƍD*|  u6hx*{9scG}TSLQٲe=#!!A'Oo+WHz1c8-M  7xO?U׮]ix5i$M<~z7լY3-HLpE˿Wُ1FСC%Iѣw *d :Tv풔w;ިW/^hz\5Mgyd,1ڲe/_M6i:~PrTV-=j޼J.mcjdULۼy4h8s/^eocƌkx-I˗Wu=@ʟ?N8C)&&F?rB4uThŽlzL>];wN~x~F V%%%… ו?~!;`B\5dȐ/$iѢEСEUhQ?Q{L+WLq; /s,R 7U`A,X225M6Mq /^֭[kɒ%ʛ7o͑#W~[>c^;ѬY ;YՔ)S/y)ZQpƍkݺujӦۗ:2ed`2dw+eZ%^78 ?p>}: /2wVXQjժi֭j۶˖-K zO|׎ixޡM4ydKN 3f)1!xիzuy^~FԳgO\RŋwިQ#S!;^Ǐ׳>+c /Ghz#5:t`s2H {}~^~F?nݪzH9sOd1v M4Iݺu 0!Δ)STZ5^~B_UV)22(WAk׮4\5fΜۜ RbB@^+IhxȔ(W 2O>Dݺus4BCC5gnd2 @^@đ^x@„ @A(Wy1 / z PP scyC{N sUV6'(W 2? 'Ntjxi1! PP ^cȑѣSkΜ94dJL+z Hȑ#;8«e˖6ט(W  Gz ^ 2 @^@ #F `0! PP &7#FP^4oHo: /2 @^@cyC5|5oT2 @^@d1vNÇכoN @fƄ @AiKqqvM q7劈ɓհn X)1%RN)dk4& Le ]5@璴T @6`4p)2%ȶ F @`@^4 Ȗ zq=<<\ , Sb@^4 kbyCɓ5qDc@Ν;8B ʗ/3gСCXc_(pz-gΜ;FTItI%\`*z?-[ܖ{ܒݻwEY>}_||d*ϟܹs۶mB ٘\ٳףTHk+}[J7;!i=q~9ή]t]w9o߾]ժU1~p\߼yjժec"pz S4HzmSpNT!r BCN+]Μ;@ʙS x?$M[%&J $/#Vi`Ś5Сv[b/_T2v?d*2-7WƌΟ; hPFΞ; h@HM/HҔ)!<ew g۶I&%/{Le i,SݓLV>kWGNhz@zfϖ.;OS87O+S}jN[$$HӦI7ڝV'7);S~; W*U M/pDž ر޽v'ŋ'7)3ѴK҄ Ү]v'EHRXIenN ${:LN; T)Lxi(Y N!ii^}M/ܶmҔ)R|I`ETvpҤIҕ+v'-ZHO=ewlXrvU]J ڝwI}fw XեԸ)5^`ܹW_ٝVEGKUڝ…vU}H5j؝"ۢV%&JSJ?dwX-*ew#͘!I`UtT)%^K ]N+InRٝo/K'JfwXQԯ+I^୓'>; T)񕙜:%!>lwXQԷ)^ J#GJg؝V<+vpvpr)u=N_>Dr$eK驧Nߒ:|$Y3gNm_;Nt7;~O; IjM/ Nj԰;E NxC~Sdy4׌fL>Bo_lYS89SZo_\9Sdi4.]J>۝V,ܤ;߮\I>gܯڝV+EGKy؝$ˢrGv'+&7)2ӧ#CN+ʗ|SYM/ÇNٝVԭ+iw gGJÆI'OڝV9s4~Mcefͤg;ݻ DiDzYSd94 #]+͜)cwXѩcvpOҴiRRI`EǎRӦvRhz@FY RZvpܹvU&թcw,d35kNʕ;?*:ZPYM/HWH|"I`E޼M~G 2? N; YSz-S8OiYM/VƏbcN+ONl>i8~C U6;iӤNl&i5~A $פڵNliSKzS8ki,S~A nf͒_?;Nli2S>G nȝ;?Iv4yeI`ExxTBv'ԩYM/'7)21cN+JN>+3pA;Vڻ$[լ));& .?nwXQ۝Ұaɿ[@@ \y1Nls.\; hHzS8;p@=Z:w$hz@j:t7;M)SkN+ڵZ;[wI_؝_KfٝV!կow g+WJ_|aw +4 =Iwiw gs$7*W;s%KNXF ܤ(X$~=yMN+BCEڝo ԩ?۝L):.\Ǝ; (Q"w*($xQ?^ڽ$hzȺ|L^:~\6L:v$JL}[ IDAT=yR#QhHzS8;p@3F:w$ᇥnNihYnjNjζl&O^; h&l&}gw-4=;+/;-djNX-Ulw gsJK؝VEG'+3;WZ@hz1ֶ mo$$HSJ?lwX*Q$3F1CZ$@hz7JHnRٝo/JK{؝V-*T,.]&Lv; *^*Uɓ؝V}wߩ#GND |aC饗Ncᆪ(o"Ev.^'`4 0j4E}6ł 7XPUTDu0 3{g]a{ hIHcNJN(4XxZtl""""""""""J@~S(u XHJѧ0h֮E'!RQt Eg۷NA5 Et Ex{NAM/""""""""""yxR'{NAʚ1h@t ENA^DDDDDDDDDDRkkIdf[.NB*V"L&': 6TJ>K__t,5@`$ ss5ed$:ID^wNBĦ˛$2XQ|Ɨ:-BCE'!-Ǧ*k+:Py"*JtRFV€%KׯE'!-Ʀ $:;wuQg_N񢓐bӋ(t,:"__[t RȑΟnd2IH EDDDDDDDDDTT݁ DP?oS&MDPtgE """"""""͕gϞ˗x)}HOOG\\tuuaff055ER`eeXYY|򰵵EEXD L<.:L&eat(: ) "RHHΞ=gBd2 $$wT^ݺuÀбcG HD3~~~I7_bĉ]hD@^@Ts$Y<֬/X4 =VNLLD! Ʀi\7n@ODZ%>>'OqYQAAA oJ*aȐ!6lg/_ .#\ll,~7]]t+#:i#:%:I `?H /7czzӐbӋ\D={#G-֯IO>1|k3f'|Ԑ;}={`ݸqd2H;<Ο?F҇Djdxjhժ;;;ёH[˗|Tt,۶ɛ;NBbv\9It@lzQS+ IIIGgϞEffHE֭[4h4hիWM6#ѣʙTʔ<=卯Di䒒uM E!Ml 64 ^DDDTddd 00 ."*RSSqQ8q222DGh׮+,X#(99'Nđ#GeTXQt$6ի̙dy~VMto+VȯE! µ%XYz50b,_/^dË1ydTT ñcǴ/LM6N:سg8DDy:s 6lcǎBڨE `D)=} ,^ z%: i˗aӋHBÇGzdDFF1h ?)))͛7ݻ7VX!: iO? BÇ5@\$)?VbcE'! DG(6VZ֭[όZFF\]]51>Ct E/7NRzw -Mtlz0h޼9\": Q/_d2QHL 8:NaKouN?]D  nݺGt">>+ұIDE[Nc윩S33ibnbVQdʛg}ӵڸQ\ /7i FR{lzSzzzGӦMѬY34k 4@ɒ% U755wޅ/O?iiiΟ'O`С8~8ttttl"ʝڷoVZyhܸqflZn֭[c֬YǦMn:DDDI???]cǎ-:xP0^6oƎ D)0=]]I԰/B:tHt,=&LAFlz)IWWcSw'''tA%6I&hҤ &Ol߾ׯ-x9y$֯_o$"Eԩ:t耎;^mѕ+Wٳݻwc RsСCϢ%*2nn&ŕ+d9z(WNޔgx5u$YN,,#D'bBWt"""6l-[cݺucGݱvZXlzYd+ʔ)q֭[E6md\2e BCCl<"m-Z`Μ9z*^z={`ܸqW6>!CBҥU:ׯ1|A+wwzu) ?.:)][t Ev׬FRkEDDDY&6my0r A6m_`齎;… 8r&L0ѣG*H*U | >sCc300+sΩle˖ *(GJI@L4r%,,GGi 卯ɓ峾Az:ijRtRsEDDDZ xܻw[nڶmED ;w.BCCqy7N^p3F3?|||p)Amʔ)`˖-xۇCjLCvvv8{,O1i&'SJ&:V<ac~ԻwUǢL/"""EDTڴi7774^;v U6'uV,V#*\]]ZOOG C&I>1m4s%5Ҹ10}:`$Y""ŋ~7Qx__I| ,Z$lmE!5U|~""""*\022`llƍCZD)FpvvƋ/T2ƍ7pK%6f K^;88>>>prr6Qt ~-_ZP]ѭ[7zhӦ '''8:: M6/ּ~:.\X鉈T'66VZ""vijk׮գ"&N>zBt Ek 2CEP!f|MlzI0`U777亱L&C@@<==QJi?:t͛o޼QADDDD/^@jjdT"Y-"nޒjҤ %G$_/ۛ j{w)]l dfNBaӋ0sL4hCFFF322q㉉XbjԨ~z Dff&tu9IdJx7"* S=\N*Y-"SId-F%㴿?nɏիQ1e дZ'#3>8o길'-DPt({T@lz'p|(]k׮&_|,"""""UȀ5{S.3CDp=nZ####޽&&&%*6<=n!̓ _Fas6qwDPs'pTlzٳgVFbӋ ((HҚÆ i(L:M6ŵőҲeД3]HțmAyĉxXt16<<33I6P>ETwc*dɒEFFl<"""""dddয়~%$ID'883f̀-Zǘ9s&;;vv& w}036l@jqއJbbˁ'OD'|`ӋBBB Gjjtqqw7ũl,"""""lذ!Ǜ c000&i/_bÆ ޽;j֬  &`޼y*MT,5oL$:Hݻl=I m_,/56 ݻwَ%$$Oec`ĈWjDDDDDRzfϞ-iM##l vJKKãGc+Vѣq)dfflYfa*OTl} 0|;!!hߎEyNNȑS(zX /:Qq t~Sͦ^ *V(iM"NZZ<(Io(DEE!22AAA Ez/edd+V`E:.Q2l p$ 5=cp0zz#ܗ__GN5`F`xPt^D`lllϜ9׫o6m`aa(.HDDDDTXXb5 0m4Ik0p@1Trؽ{7Zl): 8QޤvMtǺc%̌E)8WW * |Yt,ǏE@rr=7~xڵK%a„ ڵ+Zj1 +)) CEZZujժIZctttlx`g':}?}=^e<=ZDP{7g5)6 N:KMMŐ!C0m4ƚ5kN>H^H x5MLLO?IZc4h???o033x167wG Ai':Jɯ)ssI6qVbӋ5jLCƍq"JEDDDD$ޅ C+9sFDD97n޼ۋCT|U"oRZKL*VEP \ <~,: }{ziElٲAQKݦ}s}f͚ݿ=z@1c :::Ep"""#z 000@2ePZ5Ԯ]%K,,wA|۷oqMDFFݻwjԨQp-<޽9,--ѰaC)SP#44111044D2e`cc"Co޼!C)i]{{{L0AҚDD9iڴ)\]]1x`2"I4iL,Z$:I.YVeLX!J$")%)iixHzgw503gį8Ө0c0$Y^_fs$ ɘ>}_4֭ NY5jܽ{7_?wΝ;ZjaԨQ1bT2233ݻwxi544D6mзo_ :TOpp0Ο? ϟǓ'OD(QBqqq߱eܺu 2,[=;;;`ѰWDlٲ;vKrF__-[ķ~CWWfm6ܹΝCzzzCݺu쌾}QC"<<\K,uB /0l04nXt"Խ;|85gʕѤfMԩT ml_*T)jfdfś7OVP?|̹;w0vrl:UhtDE6N%8X\ސ+[VtǦx‡IIIhso߾zΣG0m4̜9{ѣѥK7LrM6axI ___bƌ3f O 2>##wݻw1|o˗/GÆ %FDDD9sĉꫯ>:럈ׯO>={D۶ms=$4xIq$ #:6nu~j 3 [KKZZ©iǃ""p v MΪ)upן|RcڠAfd 7\]{0lzi͛7+|Xf->s*sSSSw^ݻvvv * inݺ#G͛Y[[e˖R J,x}dP;)) 111Ri6sss:u UT)tt?o߾Ir}CD$x{{c~EM"68[9~l\Lѥqc\X '~U+1DDL(6 TKKItItŦW1cƍh׮I:+W˗x +,, fBժU1rHɚ_7oƔ)S5eT>7Osmw^|7y>nݺ8y$^6lXYb> @GqF 85BʕQ^= 0Gݻsl̙3ػw/NwJ.͛7# nnnhѢ*W5kC_Ct5׺W\ӧY={fhXh?q0z͞=7nP:ѿ2220x`ܹsG7ou>&66֭zD$[[C%@*牱ʕ*ɥ͚j1F*o|TDk?+ ^ <|(:VґiؚW׮]ѣGDEE!::Hgf\\\(Ww)))r.]0˖-|icccL<ӦMR5ЪU+*PݻJ]kkŘ4iRk.\񜁁_`۶mZo}ݺuos=_n]<|(]4N86m|nbb"Zl<~)=:9ꫯecy& wPZ5DGGg;׼ys\r%2(00u} LD֭[1bݻwգ/((5jx7$Hƍի%kllW^.C{fffcHFGGO?T H 30s7x0)*gKR)5TBvvY@Ŋ0 7T:N&MpzR~~NSWm>#?s*MiL`̛7Wƞ={ .x"""bU5TbHp/^[n?saÆ4T :4[ 7ixҥKx+P z.-- Xvm7ooPX"_ /(UT!---_>tĉl /Xpa^`jj1cxڵk? o\ /^^ / +*d2ۇcƌHIIHt%IJ~A!^ X`°RW/G֭U:O@;1TcGDP ,_ps:5j֬):Z Akԯ_gAAAAСe SRR ]%ǏÇ6 ^JKK ШQ#.yH$A>}DPlYۤT)3 R8o/'R'nd9MqMZjV*zM6Ç9sJ*MNNƀpԩ|=>11K BWԩS.WXP/ ={~g5kRs=?9rAAAَ4l0}|}}T]"""^.\!y-Z`Œ%"R`` Zj 6v,жNv",5 ? 1n㔿モR'g۷N54ebbkkk1FN|hTT gFpp0N<#Fz}t 4O֭[͛u666_bEz¢355Efr=oggΝ;+~i/%s`zz:NoF^ z~c׮]^ҙԏ.$EٲenL8GVMDZSGt E{GN#F`7QY"88Nh)PV-zJt d(QBt '''899!99'v؁cǎ!99bbb0~x:t(mڴ)s-[,^z~ÇB~_SN\|9s7Vzjժzm} ٳgs}:6oތk׮۷8w~GmV%qA8;;+u3+|yyBU".XxHtltttqd8桵GnMUuYpK"bK pCdjjÇ㈈ҥK tZ*s~~~n:inn^ହ =F^ )L(/Jӧs=sN<o߾Wtt4nݺ///9.DDD1֭$kע&"bddva֬Y8<ñl24iҤH?s ʥ oRWŋgD'ɦtɒ7kJ`/"]^_Sᢓh,6@ ;j'ND`` vؑMǏϵuʕ\'eӫVZ[ =FaLR5Ke?猫S*W7n'"...ϱT'iX>TI 믿VIm"n+V gggڿ?\]]U:V [)^^@q/ +U_J^7*6|ڵƎBQh(t,a4Up4^Ň.Kܻw={^£\W?~8,YP?TD TT) U5w DlHD8;;͛*)S6ч:v#GhSV^͛7t "ҿ?Яn֭#13u+Ws$)L>AS({X+FBD5LQ255EݺuQNQ4£Gϴ͛jٽ133Çѷo_>|8Lkv<444礤H=KK\NjtbF|Ϧm۶ׯJSfff$""/99}.\~-~'&"M&Mp9l߾&L@LLJ7n6m뫤>;Ot,gQ(0#G9=p"ָAKTQ(⯿{bN#^%KDŊW\Y`!!! tuuQF TR+WFʕadd333D7@}- !!HMMEXXr!R~}CݺuQY~mqF,\|*U);vAΘʭٔ "^y}}|)5mۆ*UQ"""v6lΞ=ǪU$"\::::t(ڵk!Cŋ!C'J&ݻdٻWsI|޺5V}x>>>>IyL~Oazzz]4Y!QQo>۷/vD$\Vf IDAT*UWWWYFwE.ym"&^N# l"o|A0|I^wO^)?DN&Tǎh ]n3ϟ//[2 C 6kԨ<0̟?|dKm۶?>BBB0{lY0fIƢ,;=֫W/T\|0yF\*QD猍y7o.>lῳ=T)33#GΝ;UR[nرcgիO?y!$$DDZJ>KKk(Ա#ML$y}Iss5{ӬbLc^5kvUVlcǎիَO0ׯ_G޽U~7!̙ooo7k֭ڵkU:pW@@@>,s+f?pɒʫUB4ʕ|ddd%!"""m%0vXlݺU%۴i(&"RX @ʏ?(y]"fo/oRH`b <\t y6ּFZ_$* X(4V57o.99>|8/_^w2<_±)S܇3N>"1㖖9^P/^(td\UVMq4ǚ^EL&Uv\˖-qq>N^۶mÇ%KڵƎBQh(lY&I<^PD^JZSmjN(<tItbO^ c咍QPnnn C(ʖ-_zӧOVVV9xݺu(Wyܸqc4˗ϳ9~d2b*߰aC;v,ŢEХKIkfdd`…$"}N`:tjSÆ(%,OJZO|嗢S(a+%*mze@Y_N:혋dv)![dxP˖-+sjiӦ9>e˖y֓ȫպuk4Jʳ)xE$%%a""""3fʕ+URv8ydDDJWW[lukΝUD5 Yt E}}gaHIt 6 -Vtid;֭[7+VZ gffbhgz'pȑ";ԩ 0''<7}d^|RJSNڶmwaϞ=EfԨQgϞu5""uUbE_sNIk?݁ DP?o p5u*Ф$mz͛7kܸq vc(fڵ+}(r?E0}ڵkَuc^[[[j*>>>Iq#ڷoy̔IDDD̙3믿vJpiب>}W֭[%GD*V"L&': ׮-i=/@ >ߧ覗 ʖ-HLrؠB WVjjjc/^t'meccCCCc.lpAjՒ$:7ce$]p._>mO YIڟzuSKmC]JHHݻws=M:Cq\RN:3f8֦~<<~i3 b۶m9reN'ka?>OH2e/FD$>3Yk9rDzD6m)4I/A3=-QNz))2-['NAEr۫?$ڵk|.gw$kZ;&N( W⣏>QECСCѣG\i8:u*5jTZO&Bg3gyOdddmjժ3kvލQFiZh T:0 ?ghFӦMOD$'|"kK.ZBݻ% h>4 +gJ%JϞNAZbKKr(?߇Jķ~y+++)?ر#4͛ѶmM%kkk]PK,#?,twoqm۶^:'11NΝ;y>)) ˖-+t]غukB9s&ի5k׮Ev |oz ƍ_lDDDdVX.]4:͛+RH$WWWT\Yz˗/eG uKtֈ@׮Sh:}Xg]VlzgݻNAZ`KKիWqٳg֭[@C'''&* 93gP^=;6rsMt 14ΙbÆ / {Aٲes դ{w76שSww7/ލG}׍ĠA: ;w,p]Z%K`:t( \ ;v쀃Cם;w5ȑ#qͬ{!Ν WWW,Yq[[[ܹeUV/|o>lRDDcǎ͛7eG {(s3SMNi.`6+kӫ-oo?epR<~^~Z֭[s} -/_~@ƍڴs#֒m3'==K.Eѯ_?Z QQQ9Sոw,YmۢaÆ8tPlll}vO܍7ƩSPj碢Ю];;vuΞ==8ުU+5vZL6 uƍz1b-Z `ٲe5jj׮ׯnJJ 6ms4ܹL6 4q/+v,kƱcP||KJJB@@:uKKKTV m۶EfP|yKncc}zI"""2iժBCCeG [ 06ǏtH+^YYH+^(3a&ĉQN <|7o^%j5޽3gɓػw/>7k(y{ԩ׿>={`Ϟ=RJrʰϟ;fӦMvڷ.6Ǖ+WcvSTT:w.] ͚5Cr吙\r;v5T*xxxyrܽ{y?WիoL/>rƎ[^K.۷c֬YZ]f ֬Y{{{ywq% <gϞ}iiixQ&UZ{AÆ Ϻu0rHE>|gnn۷C&"75jԐ^xxHaҥ=Pg1$@24CUIN/)=IIKJScc pZZneڛ/_(UlmmaffkkkXZZK|111ǣG\M4qI$#'''XZZ}/QFARaݺuӧѣ9Kܽ{@cUX>>>=z4LLOlٲرc///zc#Gȑ#ռys,X 0^J%K`9u+(###1sy벉DDDd6mڄ#F(ڳg>Ck飚5kZ/""Bzy@ =SԤ>]tltO`rBKZCRV U&5SM M/-o߾Oӧ ]Νu>fqRPZ5_uZl#GΝ;عs'?أ+7NNNh߾=^zxTa =zl2>|laa={bM4Kzsilٳgjiidd?>|8zj\v@ﵶ<<<иqcmٱcLkb֭lxQRreYfgR6²_g"el L,X :IpOJΦMɓ__I-*c%J޽EG(VV\ :֭ubDxx8BBB/^~(QL899Ut民>} !!Nµk8 Y&5km۾~4mT"#Ch PB Ə#""sBCC4XYY5j@˖-{FH^"""ҽ]vaСSacӧ쵉 ,,,r-mV!=qԡ'P!$@Ϟ@LX4 (UJaz%[-+>o>P v$6yԩ;&:J6mFQh1Ujz:%uSʕADDDz:t(eeMDdlll"I`ر#_NY7P`K DVRZ°arNBy0~Y'Odv! 0622š5k0tPk 333j)ұ]mD mMȰ%nUgϐ)>ӄ @VSP* b̙cI&pssdtORd 6LDD$11QZE&Ć Sgg)4m>,{ِ'OdWM 7P 6dnݺ VӧO5/^ѣG^Dr2bp$ I>Z Z\*kًZϹ|yYP$vDd`ll۷kדR|}}Q1rCRpB;VDD&)) ճ 6U$.NBBd+ymj@56VL%¦Lq!CtL4 cLO^RܹsGDDѣGLb&<D'!m4o.O"#{"RqBeLhk)_ ooo||Jo:q9t /_T?S*RZtҲ#=p>d IaݺÇN)(XHH(R !^IU;#GNA`Kf&&&pi4o\g:99?3t6&)իѣb ^`r7eGzY` 3Stưa@^Sh:^ӵ. /]M2Wt ^ypylٲE旅L[nCDDDDDu5tԟ9s&OHm""CvyYըQCzGm5aЪlYj5uݺPT,־ hFtM/T*… 믿W_rʲԮS[,uHׯsΈS'fϞHm""CVq)YkUm9":i ^]t M7m.\XY[Wz%7P%%EVЪU+미}6.\/ÇǓ'OrgҥQreTT CƍѮ];N'""""*ݻ?2?'ODD͛xlJ*'''JIVƍE²||OOsi$ii@@tOb&{lz4 DbKT*ׯcĈ9gdd˗DDDDD$ѡCDEE)RS6QqEe*k=Sqq?0s&": V*RbTI=,Z̘`7p-Y#9g,ibEDzvvvlx0AAAر#>}H#F`ٲeܗ(X~5۷o/k=c/Ia_-:Ǐ??|d}NB%FRtM/""""""ѡC<~C m}Xb^DD.k_z炂ŋ/D'!mt 2P….?۲=dYtNQEDDDDD$PXXtHE껹aʕ02_򒕕ٳgZڵ&sի IHC}N%`*i7Dcʕifb[^Drs Dz |PE47n"M6ʕ+lԨʔ)#kM2&:ihFt M7kFW׵Y3^;r:c":@I{ի Ǐ/^ôiD$"""""@CE6m GD8xyy^w޲$y3`ot.: i$oIT^WKjh-||X@(I ZGbƍطoaÆnzaΜ9 ?mقWɺcG]7c!矕ѯ)o+:EŦLn߾ኍadd'Ncǎ9ΥsQl|"""""ѽ{wܼyS[Ea~ `۶m^z!55USLc%`*i/*L[.~7n3]4܀'zZq*98s@G HɎM/<}~!bcclٲ8tƍ IDATZ7f͚x"""""*t/_V~Vp*UJDT|ܹsnnnxqJ>>?iii4iFtEprrM͢S f  _ -ر !C:kر(ee5wwC)6HVcĈ٘&&&Xx1<==s=waӦM:CDDDDDKHH@׮]q%E7j;J.H}"*ѵkWɺZ"L7nUV7ox{{Z1m(5JImh=aLe#KeU ] #FNBqqx':mmqQQt"E饥t[1fM4:j믡Ve:{&<<c(NRaʕh׮(T\l 88}NBŘ- W&: bҍ7r=ާO[WT2e .][ʚJ*bbb xDDDDDDDD%J?>Q8֬_[NC^ EG!R*eK7o۷nݺɞL2Wոw$&&&Xb<<|Xڵkc֭8r^leAx{ˋNMfN"J%:^za'M'N(H$6:55syW^}ƖUTQ""""""""'ǴiPF Q,Y[nᣏ>Drt||76+`RIٲ%[ơs&031I68 Kaߜ$wܱcKKspB,áCЬY3ܻw/ʖ- *(̙3AAAuf͚:uꈎ5je˖Ç3f LJCt4KDGN"K п?mߎm3f^Pret<OHЩ(bb ɍM/-n:1ZQFaҤIHIIQl@ݻwG\\[oҤ TKDDDDDDTիW| ܹcҥ EGSSS 8NիW1zhXZZEm[૯DԤD/Z[cPX:a^MvT|޵kjYw4xX<{&WE"-iHMM8V1o}O>>}'OĹsp5EWQBʕѮ];t{F2eDG"C4j ܱcd;u ppNy/]=[Dϖ-_\7>Dpd$?WdT]4>Ϟq#.GM">|xMKHH別ٙ򂗗DDDDNٲeѷo_[nҥKtnܸ;w AOfMvhݺ5ڶmvڡjժcQq1m \&:I;&ŠAJ-ZhNH@pd$B|8/^˗/äI`oo/:@6662T|Ԭ)5)f$[L 0>0cP4TX..R3GtlqqtONCTh\n:+&O,:Q{Sh gD'!mh!'/$DƦ ֭ŋCR@ڼuڵxc)4ݻ5E{wD$S,GKd(ٳgXt)Zh!:QNY`:@@ϞSh/`j #CtcKF3f?(lƗJ_Ę<hDt Mv[NAښ8QZPl":Q%3///lڴ Kذa&NqJ,oojU)4m;&:ipqBCS^ 2d];d&MŋX&""""""""*lm&$ْˁkD'!mX[K$RS+˗E'!z+6cǎahР"cTT ˖-ŋQNE """"""""|HM }!!6Vտ{s`BIŦׯСC8p T۷իѣGȈDDDDDDDDD´hxzN)"D'!m4i"O"#{Id":@IRеkWt8qΟ?/ CJJJ-_<ѤIj ;wFuQwE')@RPa}! ^-:I `"`TZlz阥%z=zhGRRRSSK ,,,D$""""""""3 &8p@tl%5MƍL8|tO':I UD!rzvvvcQQxzJs.\$۾}='6Ə_%:I{jpI4p3(4o<4m /GDDDDDDDDDT*ݏ ~l >,:iUKt M[׬F"e6l؀+W`׮]:u*W/ҧ=RS+˗E'!m^^>.-sOcKK8zhs):nhh(^#˚5kаaC?~\񉈈V$>Xx@tFR3U$$AA`Kk5jt5fE KS###ѥKlٲE DDDDDDDDDTM'N)2<Ѩ0m<|}{H06K.B|_/FFYYY>|8g|?Bt MAA위IH:FN)$DE/: plzA۶m" NBr4Υb^cwo)4]H2<_t M/KƥNB%^E,:ڶm@-[VxLL f͚%(i?hJt Mq3ru0iTUڵkcٲe9/]Q@͚Shڼ8p@t Җ7*:[}DM"AA222vZAHԤ$[z:j!SSpt$[Fz5矢P ĦW1ңG6lZ- P' @P${J$˗ߢP æW1RjBCCqi(WӦNcO O{*"Bt*A*Fs=~'!""""""""|u):_E'!mt|BB .Nt*!*FnݺӧO8 Ր!@߾Sh|XHM1h0p]/E'Mbڵkؾ}{ݻ4DDDDDDDDDT _}m+:ÇMD m|=E:a":>HJJB\xycPݻy.o>@zShںؽ[t ҖShڱؾ]t *&ʃ &L˗/iӦhppp D'ɖ] 9#: iTItlj4II`-ԩgb;1H[I/Iz,] ܻ': ilY27$[Rۢcӫͱe|駢ܹsQfM1(֕$* E'!mԪ%-uOyPIȀU@&&&@.]DG)3gDDDDDDDDD$v퀱cE ̟ĈNBhBSX ^`bb-[raΝ={(DDDDDDDDD$77)4ݼ)-K$: iO`P)4ݽ ,Y"-ITHlz6m=YCժUCݱvZc݁Et~#]Et4nVNBF?:7Yf]ŋѲeK={ȀU.5)IL 0>*: iZ5Kt Mqq?": 6dpelwcǎa޼yE/88qqq9ǣG|r """""""""ֲ%0a__ *JtF_$2R>^E޽{#11@CVi̺uڵkhڴis/^@=\1 &:%K/E'!mt .: `" !AtlzٻضMt ք @VShڿ7)H06`ZQ KƦM`nn?0""""""""""T&:#GD myyիNif)H 6c>|sqq;#k =t̛7Oֱ*x{66dKIV^aeeʈN-- .^aKK@f!לUT;w"y&MJʕ+ Yj|铸8 QSϟlziܹsV}ԬYfff{G)Zjh֬Y㉉8t"ci\KDFӧ67Zt M~~үT饥+W8ɓ￑O˗?ѽ{wE3c]qȀt .: `b IH;#FN)8XPE%^Zxmoo~ FFJMLL dF'ODDDDDDDDDd zBӹs@F$C>}Dtjlzi)..N!C`ii)(&M(l"""""""""-Eд?oS<<6mDt y#lzɤu֢# 111joq"""""""""{@Shڼ8p@t Җ7PlT饥7guUTIPl>ܳgt )#:I4 |Yt҆rp$[z:f p$06dmm:))IPlgϞ7$""""""""\U"5)6*VDД,ZNB bKK*Tx}5AI$7n+W<4DDDDDDDDDdP7LBǀ+wտӧ/!: )M/-U\Y둕%$ZɓJGi u !:``Biww)4@\$6TF wŏ?($95:JCDDDDDDDDDkPO)4]Z%E 0@t M׮ITj$$3 Uƍs9s&2220k,T*3cԩX`[ZyIIF )ӘSDSF%KASaH<}[n… Z7ڴiHDDDDDDDDD%TJ0i$Ef\͚Pa99I/OO +KtɫW%RPj.V/Ec޽{ѻwovZXBgVrr2\uF`mm-0QRRRpׯ6l}Qaac樃;9)4YXUff6^Edn{htΘ| gzAѸq/3(B2eйsgJDڵk#EG "zDD21RR )8IM~O5:z=hԩ#c5.EDd8ӫO?,+++L0Ah:aÆׯ rs_!CQ`""l.^ΝH6jBuE.NV-ww5_!bIQD-ҢjPE[ZFդZrί۴k,.:Lcߗ !BBV ?>SK}q]^N~{|ŏ M/.y ѡCIX>}:>⺞DD C&Mxꩧ,<<5'NE .xADHWW#+/7x]_KJƏ.!g+q~k_}=]vxw3,d2Ig=qtL&Z5<`*djՒ0+(V.B:OqIO)(_<֯_eJQIWt+,. T 89I؟ҥkɞaÜ5!;'"#u\{uؼՁW~t,\wlX C]S@Nt ,^ڸ|K'شiv튓'Ovlܸ͚5k """"""g@`0 ۴k0ctY^zMd{~s дi@rʸ(_L*s__5S8 0POթ#]CC`,uMխ+]Crسg:t r~|7իHGݻÇKWX ;wKH]Q+,ED%ENر^nݒ.!-ڷ&Lt0{q襳+bCaÆؿ?Sl$"""""""+{5h?rXΖ.!- tlZ֓l/HWX:sXHO.!-^z P9-JjZt >P,!=׿uk6T%f9^=.oHDDDDDDDdKvF̛$'K:cJWXzU]SII%E "#Ր"!Ah8Qj@/]B4O҉e@Vt iNRKeI3*^RUFrjy:=}&]a),L-ɚ*]BЋ0]+H1cS_FuըQT#ٷX8Q !]a)4X ϗ.!pEDDDDDDDdL&۷KWV&H[+HS~FeEidOҶml0mPtYn.r%pt i_5jH's.!L&SҚ5޽մi@z֭SO(AVV~G|ӧ|}}888L2TG$j8a2aQ=N.^.!-VU7KRS^Laa%E@@PtYzΝ.!-*TPהtYFڇ)C/+:y$Fjժ^ϟGRR2~ۄ3''o~>EDDDDDDT tSrOxC6_?` KΩeӥKH>}^dMM.!-zxCRxRRKJt4g+HNNv빹>}:z4lر/#g`: j(kW KWV9Á=+,eCᗑ<\ J(zdѢE?|TRXp!K8=`#ѝ;wsθuLj/#ټ[ jTys K|l"]AZM l)]aiV 8XWM% ^: |o~GTPᾯ=z{Ffff88W%fW_"]BZkKWk{JV&PtuөdL&K҆ ?JWzQJJ f'sغu+ڡC0hQZU )KRSE9d{TQ7p?JLz9ҢbEuMJed}O.!-\]5UtYVlptIWEݵkW]KkW0||?#ץKHFiӤ+,%&g%ާyҢn]]SoWH=ݻ3ghÀM2{t:KWXTCҢ];`D KׯfUmڨ$*J KH-վqFrㆺbbK^EvZJg`ĉѣ}ߺu P ttZlO>kIWXpA-iuAϞС.],RRKH=å+,AA;%vC"XftsARW_ݻUDDDDDDDd(F]HWXڳ0`p{w Kz5PP ]BZ~ʕ@nt ikjHo$G˗%vC/nܸÇKgnݺzjj*6n(PDDDDDDDDƌWWW4|oMԮ-]aVPڻW2u+,[tie2^^6lv풮 M5a~8ȑ#ڐ!CpY\z7nħ~)S`„ Xx1;777ݛy~Q Q,]b,Y"SAoM22ԞqOKԊK̲eˀǥKH gguMU,]b9|Ĭ C/.]tkXj֬Y__߇7&L{SjzRRu?٨Fԧލ$!=.!- e#u ;zUWxTr20>pt iFrn8ڵk6~x 6~Ez'lٲZTT#"""""""֮_fR0=<L$]a)* 9.!-ɓ+,ݸ11%Eԩbbԍ%vC/-~_LL+'ʗ/w$GDDDDDDD6o_W+,],ZJzCJWXt XHI.!-zιsG5JRDz03SqQFF[n5k (/^|5#""""""l{w Kz5=mo={JWX:xX ˓.!-^{ Gґ#j_&=/$]aq4ӛPٞ={ҥKc ٔZu+ytii#]a;+Hm+,qti5aоt˗ l^+Wgdd 88_s/n] Kk!!U@Ptۥ (L&aC K6;vHWV&-]A:K?uYxG!>>_\r1ͩTI )\\K22%KӧKH K̲ (ʕSהu˗89ӦUJN8ҨN:x"BCCEZ6oތ/^"""""""4hn(ɭ[ܹի%Ez2^<=>u`U5jkAtF 4{ڱd :O\;V]S{JziTT) ,x\r ZJPt@LJJB%ٳ."""""""0]nub ^Q@%ɤ%f6)GMOIUFz<&П;']B `رp(}6nݺƻ9 IDATd^ХKxzzv<""""""B,N.!-ʗW7*T.1/Wˈ)WN]S+KeT#=>'' @ "7XXO5spPoЫ|}}M:Ǝ+@DDDDDD ;.!-M̟DDHꆲܾ /Kj{W]S.Iժ#̳%z_|ҥKKg>ƻ8*͛㭷ޒx?Jb""""""2Eaä+,')ܑ.!-uFt 0o,]BZt #]a)"B=휔$]BZt/]A̚5 ͚5ΰ0dK:~U@^t i1dлtÇ~L:Nh`ॗ+,?,b嗥+,:,] dfJJWcKe˖Epp0ݥS 6ļy3` wm+,iti5aоt;+Hc+, [']AZ t"]AC/x{{ᅦh;;T\Y/_ti4l(]ai` * hXƍKWVӦMJWX V zJC/m9͚5oooJf&lpt inU$]b;zT(S0\`J!ҢT)uMU.]b|/%մi@zGA݋:t#GDDDDDDdKsҢNuCHn˗KHZwMݽKKHj5(]b ,^ IUkYC/+Y&v܉%K?EP|y| {ŒHnfbcKH-)S+,ݼ7KHf͌7hǸTTt iѨzڙ Ǯ^غu+^{5VZ?OX=z4.]O>5j0a0e8 DDDDDDDsG 9Rҕ+/=;GKWXPO&%I:IWXfKH~[n^IIIԩ 6󈉉1g4ogΜzMHcqk'''t .ĵk0|ڡDDDDDDd/VR{<Wұcj/=KWX:u XTIHu]ٳ%@zt iѷ/vxPNNz#G<{1p@;wXrvv+W^y ñcpDFF"&&YYYHKK*UWWWԫW^^^hѢZjrYH!%on%f;wa%Ÿq)KvVהўn3zBBKQ#]BZ۵K`Cs>r/D^ʬTR񁏏O&lؠn)]BZL?ptƍ[6MP>ȇr!]#Rq PbX mTjN.)nyÂ,X౿V!""""""BV.!-ʔQ/\tC%ERꚪ^]Ch|૯T#N@PtYAzj ԑ(nuEDFF>)Sz1DDDDDDD@Ppt iQRɝ;@xt iQ\@3SSE0COpZx.O5Jԓ..%%ǩS |+WÈAAυуܼ iL )$69.!-6U7$.NOEEIo@̚2 wM0v7JLL|X"v\?ڶmZjlٲ_>|M#""""""?|?_=EK`h Kܹ@Rt iѱ#0ntH`lٞ~[j ]BZ< 0itEewC&M}ݺuyfT\Yو,fz_dɒ{'CDDDDDDTdǎ˗ /]a)`R 3S_]WFrӥKH}W_tZ!z (=֮] ???$&&"//uRJE>V\+V# `Nʰa%Ÿq)KvVF.!-FVTHtYHt i1bzjn55n ]C5lzڹSD'\I\rТE ./Ct~զM+]AZ=$8; * @g$_ |ti5e }dغؼYz]uk^\1*&&F::rr+ÇKH GG5VM,/O]S%Ԫ%]aVP^mpL&N KkF* __İWݺu{-77_rrrEu"""""""s åKHͿR% ,ZIzȖ'"^LK*ӦXQLLF3YҢ|y>UtI`ЇIp9]äDEE!::Z:Ⱥbc365mn(I\DEIF̞ \&]BZ4h`k*1Q]S%Ezig:zUR^^^v>Cff.2ړ^[lN """"""*@Rt iѱ#0ntH`- +,] %3#]a)*J}#.NhZEVewC/h۶_OMMdBÆ  K!i蕒YfIg'ˁ,Ң` KgK%K/KWX:^-u&]BZ "]aE$kJt i av.^}yףQFhԨFe˖!44IXDD}\v }E'"""""f.`: jsg K!!ڵȑ@n ((.!- S # VKH!C@Q:zr!##/7<<X|*U ʕ+J*sn{="">>>p)o߾k׮!77XGDDDDDƖdܾ}?Ґ;wX| *|UUe i xx}J&ګYVT%Ŕ)jʓ'K̶nU%ŻkQV5ӍxyGwtݱˡ^u,[L1n߾۷ok .h>/ѣ$&&~]v FLL nMJPfMԩS>>>A&M*Ur"+Wj_=&|tRPY;Jא&@2uԐhO7ڸ\DǾi#F11@t֮Jڷ/֨R.Vާ\.+v9wy+V@~~t ٰL\xaaa77n >>YYYsp]F*VRJB ]6WOO{lڴ)P]8MDE3gܹs8{,Ξ=ֲ?xڵk^^^ر#yt^^^DHjqc*,";[FIMU{1yx>>5TX* AgB=6`4gp~v;[anܸGȑ#8upڵBœv~+|||O駟FN_lҝ;wp19rGѣGmOIDD"""j*@ݺuѯ_?+h׮d䊋Ӂ:uk7VC?|0@15YZ6TӥKٳ>Փ!"iuߗ.v;O>۶mCbbt L~~>N>ȑ#qFwݻ5kk׮֭^xT^ػȺo>ݻ{ůڵk3g̙:u`=z44i"F%ի9t V @Pc}VZHOՐjUkֶ;g\(`L5Qj//. vN5zj~ʐ.]‚ 0`TV -[ɓuV`ڵx7QvmX~=ӥӈH޽SNEVPF +X`Ο?o?ٳcvG6M/ vYF SKIO{CHWXxQ-ɚjȖt"'gϞOa2Sؾ};mۆK.I'Z^^v؁;vB 8p }% -//۷o޽{g;wD7}iӦ裏п$*IBBԓ^o%]BZ۽_0@Nq6߯3mϰaڹS,4T5~<}~W-HnSJgQ11l0T^;vČ3lrg)))X|95k[,HDr]sř3gJΟ?_~m۶Łs$k`V jy~3NjFٲE"ݶ  "xW"^gaɒ%(St ,//?F5jo߾Xz5nݺ%fغu+ڴi=zIDDh߾=|Mܾ}[:J`j& x) dj5k={$ (Ǝ.ߺuO?IWم3ѣGh۶t ĉxQV-t+V@rrtVڵkZh{l@AA+ Tii-[Ԑbz 0mJG۶M]SK&}.1{2DȦ'E`OUZDDDd0YYY8p VFoѳgOdaoL&SҚ5= H%)ׯ~Y2 +,mtMˎ⋨\tL^^FkJ= )ʕ rKӁ%K>_dXvǥ3OFT#=> @=jYYeJHNNFʕv0͛c?FDDD#Ff͚ڵtlٲ_>֭ڵkzpww2e"%%HLLDBBbccK.!<<Vɓ'{a >.1KLfOՓ?y,]*Q8IIܹ򒮡ª[W L.1}7O]SO>)]CM"WBB>3lٲׯ_zV_K.ui"T*Uʕ+lٲpss  ::QQQst2rrr0p@8p7!BV|}}Ѵi{lРjԨ p|?TpIIIݻ7?77b??=777M6hӦ Zn:u98vZ QPP#F… (sR1G=a~tYXp!0y2Pt X{78 ]x8`lO[%f/kq-[eFzz5* ?:U-CLd'r蕘^z=p<[v믿_]:&9997k~~~ *UN:SNbؼy3֮][O]|XlY ;v?N:E(]t=hݺ5Zn>| f̘ŰHbb">c̝;b4|%f0~"Ttim~!>$l߾'OFV 53''' 4GŦMkѢEyM IWXڶ ؼYuo Urfv`& ղFcati5~<Сt]u+tcwCl^?""B*WcǎYfIDDDWfM;8t"""`兖3rqq|ח_~iyG۷~."RT!C`M6r=3g`ĉpppڹrrr`㓠n] K?,]Qbm޿[??X4]XKm`pM5iJWV&#]a)8XO5 N>]7^#DDDd˗ȑ#h̙3 BllUAT… ѭ[7899IgYUq!-wFnIR0w.Pa&.Xd?zu`6:m`|eҢvmėܹ,Xp_TzH7MI..^.!*JɅ K.믿ij>ڵkh@xWc„ XtV 0WΑ?V;>QIv~Ȯƍc޽V{Zbrrrrl֦ /]a): KJ>!!ye˔Q/ ݸ̚<>d@O=L*]a)&9S][d{|}Hԟ}QQ%Dz>}0zh4nO>$^u޽e~! tGAAaҥ8p_-!""*Ne˖Ÿqp%sVc8,صkW8ܹSA tKԓ))%vNZΝ[c~Eti\"+ S:d{vF̛)Iԩ0vtW9s[K4Wr d!<<k׮Oµk_FIVSfMWZ%:ٗcذaw׮]o?ہM+RȩSXYaFe+; +H+,_/]AZt,]a)$$z=fWZZZz\X^\×Jebɸr OnXC GGGݏ}%8p@Q3<<W^d@&%]aiO*-3g*ozmXU6mR/M&#]a)8X 6}$8غUPnUn^+((… u?,\xQ:j1rHcƌpwwN*v;wɓruYDTrժU ?t?u?&\tYVZqҥn..X;:uM9"]BZ8:!Eժ%fj9V~698?IAz5t cW ?k׮\Fz;w*OAӦMqY,[ 9qƺwݺhر(UJ߿z~%Dݺꆲ$'WHؼcqH[m͝;@P.]BZԨ%f))…MUӦF ,^ t c |hNN^z… hC\c옏3 lٲ1c u?.lGgЫf7n@P*ҳ/²5_ԱJPL 0sũ(xTB0{6pt _Z?|}}ѹsgZ c˗1| """*&}A6mt?}t?&Q^t=^%L9wHؤV!g'',4 Fz""yӄd{:uƎtKH +,EFs AAAz~~>݋{3ϠI&Fj *B \.1336mXYGľ}0yd9} 0@c۷CDD]vxQzk@b"PtuڏiLq8, sHǘ>dqLn(<(]bcy,Y99|hV>8P*ٵ 0ӍxƌQS}~Y]SFIFk知KSԘ1ώ7vb"""")Sus%]GDx'u;^vv6u;ِaC K6֬H?__NcWT#ɤ2``V jTys K[_DdC/?~<ʗ//ADDDdU=zxH^Ddz>Ub+n(W,]b9mTdc""s$ҢC`x KjH ]BZkL(]au5L.!`C/~ ggg""""{蕚 ##Cc)SF!٨A^t8l%]"./?nn;V*td (~{;KO.!-^{MRXZ$t 6lt jI6^Dd )))vpppxd&Lڷk~t͛q{R?*UҩȆ3nti5jz:H֬ KHÁ=+,W"q%bQ^=""""]UPA4u\Sc09F l.]!Rt4ߚ5E:Fz.:ف͛o NUɖ-٦ɓ-+,mݪ#Tb^s'st\2!<<\c=䓺섓0mPtYn.jptI/(YQ%]ߟOuQ~>W/HV@fi{KH+ [WҺuT"!%jBfŋ1f.yHDDD6/33SQvv6"""t;^K}F uCHKKoΞ-1>>^5kTdGRSE9d{TQC ggt׹s%EŊE!|_~%ƍg}+VDFPPLq`ܸqhذ!ʗ/&MUVxgO =6o\,=Dd|z-$$DcUPC/zΝ1c+,EDs窛vo͞"X44 Kȑ"#Ր"!Ah8QYlU6t(`Lb(`$KFVЪU~O^^ݻwcҤI}Ϸ~;Z3բwTTW.BT+VPĮwE &` {Î{DEPDqH&Rso-ֻ8{q'55U9&&&r?6)) J^bŊPb#=zÇx%իWLܸq7nIn:%$HU$/LBÇղ>zDC+\$ÇrONN}wɓ8TMXZۛwO7mߡ~zLN aZA88cjnIr~ [͕̚DEUH:::J.kӫL2jF(]vhݺ5tJ]vaܸq#55U/nnnXjhY\]]dtaL8?|/_^dׯw.R:B:(Y^XLB۷o㉒{[nݘ"n * |w~~f 1[*U aHct%Lz|F^Ν$ǵk90q"; )aÄ>N$͛I.#$6W(JWW˗/'I5Fe˖}8p N>;w`ڵȵv%' ---iSBSj_~"Bp(M\1 ?_KK `h`0\\y :;QԬY@ƼS:rDx"itaC)9~\Z3eF֪iڴ)*TPQ `ڴiƓ'O7600@ƍaccSSS܎;"++ @``{T+WZBrT' 7ƤIG{>DVV,\/^f͚z{ӧOEHgϞ!%%Y5j0E!w!b @yR8ڀpP*{de;w 3)lmyabs8vR5v F%0x'dg ˙NC ̘{;I{1eg;$ 8<?ǏARj*SSՅ!uuaT `Z*. +33T(]&&k6?}oވS_ ;w杄h8jzizQK"7o"::woB2001qD*GѣǷ?~V¦M iԨ/_{{{ޜEԲeKl۟CCCkILW\uf;k^ ;B4\fdS:u#]4i7ZCKK SNeR1K\PF cnԪ;R>}7*UƌaHLMs.(wv$% {ƙuNC dɜ1M=́ x&":^ (a_0 +ssԫRMQLFFӁx뱐 l",ڤ 4DtҼ#<`ԩx)b)ccc\znnn 7rSF _Ǐ/FݻwǍ7ЩS'lxB صkFsfffbʔ)6VZm_CBBrՄ>(RA`B#+ݻǬ^^Р_t#JSG,%?߸:y|U)S`bl(Ԩ!H`J[I"VR^ q=BefeƳgpݶ Əe>oڅn1kx@ZFB>}±7p.?Vu<"cc?A{֬y'!^>>>~&N;KKK:uJeYV^-3c]]ZZZb޽(Vhyx5kε\%lԨZjsGo1GQ>}M%7---k׎Y=BHѵo>{`ѢE"M`$)d}+4)^DU%˗qujto?i(j8:N!{` FO3BCww,+DXc~<4৯_s ',CKGGw/YRI`L!&#$DCQK4nӴiSޑH.Z|6lyƏɓ'k 37 NNNS*W}" !Eff&zk׆%zĉ1b% Z!yߟw YO%%NR(QqqpZ^%u_gO` )dx!,; QDn/N!k`:,' |QffqK߱5F@Iu$ j.ȑTD` v- D3PK ZZZ011GWJ#ݻwԩJƓ> |۰accc$ȑ#ҒX9sϞ=cZsR&8̜97ofZWWW?.!pq|wз/$:q_Ta\nIY˜wGқH3}֝;8vLS11c~9z{ˠA(idN>uK5!q065a= //o?W\AGG>DEi&n~/{yիWs=-C) U-[2I|8r֭˼;Znͼ.!77ʊw=@"61X]^zz쬒6TTw Y{/NAT;{sx /!9˽a=|8V9f⹺֪ 'ӧy ^&88wRWQϥ,۹f͚*NBQW݃ӚCռ Odgg̙3hٲ%~'2?ѣ1{lu T,:ƍ,M۰"#1oԮXQ" )UJSŋN#9Y3#I"&f<[h2q"@CCaLF[JkV#Qk yD_"***c˗WqB:ƴiӘ/!D߿amm D>}i&jլ)\H`J[Iv(9ã^*=`DTTTz5; QDT)֬{y-Z8:b-y*_^zSu@P$DPK!%%w BH>򛍩, !ׯ_gZwިVӚgbhԨ*Us犺 A֭GG)d{X!4$"1%VDvv5 |3BCww "w&M3yxx>NB##3˽S6Um Sye&DN)hٷowTR*LBQGɘ+5 ! HIIA\\|/_ << ‹/š6mݡMgEWD*^:6MX⌳6ǏJ՘ڧgc(u&̰ڽw_63fHk<".]FΝ ֮fJ䝆G@` ZLC CÆ?NS[>\^OO`i-H 54D@@:;!oY$}@$`ɒ%xӚ?#6mʴ&!D.\@Nx!֮]QFBQ&1u*`fLp\Gt5' IDATR5*.#F . .(9;IaLMЌZ3t:u$9nmLx!5>΅ɘأ8PU./¾qS5DiB 1z#'X|9R[˝R4o"0kШvT]BYkalh(1h֌w Yǎ>>SE99-[N!))H2231sl|Vs@QKb̙W޼y;!D#BPRR6"VP>}ЦM5 !D^FFFXbnܸڵkCHWWB)d\n۶'١.ҫ+P ֬FR85N杂OooL۰!@Z Tij$j^j&-- DNPfMxxxEtB{%(bܹxӚ`ZB䡧qט6mthy-"5Jnn@⼓HNGTz[/^`ѣJ0+Q+&L`HM VjσE cJJ{ ޽; #G }\lܺ; Q3R_ܹsQ|y.\P҄6xG cڵΞ=Uv1!DaҤIx6mڄrDHުU.(KITz5>,c2rD.UQ*5VL_1E&kAA.L${`YnQ+'8aL~; Q#/JMMűc???jr"#88!!!D||D (N7$9rT>xhlm6E кn|gm&hbm AC׮aۙ3rŒP`߾˜Ui|=|l,.Vw"a˗رclق(qQ{aĈxYǍaaaDFF"#/\o޼֭['Y.pB͛7}%-7AHo>lذy*U`ŊB?|ӧc]6u놾}y4ۃ3&ݻ8v 07 bZ{@xլ ~bHC99 EIaNBS)Sשy'q:5|8:hW>zn^_3x)el1aq7eh(ƭ\}5a \,rpASGNB$Lw(%%DNPvm,_^Hٿ?ڶm+*Q&Lg"11x glٲVVVԩSqEUAi }0## cǎe^W[[;wD%&ܼxhٲ%*U}w,B T;}癕{?W69;CG.9BӼSE5kN!nW a~{TTijvgeK8Wc QIGNA$fzϱk.l޼_~9sٳhӦ 4hҥKT 111Ell,"""p-ݻQѻwo$%%1=k,iӆy]BGhh(-[?]vŌ3о}{ޱ)cc@B4`w%Ah)9`vhTR5sTL 44aI:ssY3iHa 3Rl.-E?!uꄱ]ajO U,-mLL c<<08XsN߸͛jk cjt -;T*+K坆H5DǏK%Hݻwcݼ0; <ϊ+lx8Fa@@.]uVZUVywpp<Xd :v(V4Bǣk׮M4E%‰'p nK,Avx"*KVV"\m(^~V,VURQ0oj9*ilIrkT?4)".NS@ {ˆΝQHsZkkY.\ aQZq^~N鎩2"^ M4AӦMhޱl߾kiiDž?y߿!7ggg;vy]---l߾UHVQUA4ށPvmlٲwBS ֭bc lYɩJ~\bHraSp0ft^$ӡ ^JyFx]hkv.٘;v6mE;B޾VrDhyC}!A*za``-Ze˖UahhTTÇxΞ=0Fs~ |!6_nܸqG!Z k֬[cegg@JDGGcر8y$v؁"LCQQu ر<Е#R45r]\WG7C)=`VQ/שÇy'!y3w(K㲇:ϝ;/_rׯ#(<˕?$)Qί'O//Y+yRP\\87 5~x,]'33!9KBpt.(Ki3g}s6gDaիpASRݨJܲe52k~ر˜xv7~<$DUHڻw/y!M67a/͛zkkkhiizǺYj,dgB|}}1rHdee1]re8p $D4ocR+>>HMMEdd$m% 39Waz 8uQhUWW`ta)8ؿ_C1nJ$$'+un-Z6mA1$9TE gx'q0zD42©%Kld(c~=Æ<..B㫐[f"^r7֯_/ׯ|rܿB˫"Hڅ 0ddff2maaӧOܜymB_%J@&MTrt~o˗q^%(ѣSy QHӥQZm0Y\IP4%F''j\ 3OMFtТ4s23;1պ54jǬD Y:IJ_I:u @u*< %m; Hw) aeeSË%ڵ /FFPBm[nů;!̭[ЧO<(QΜ9CKB5 v»wS*{IHH@Ϟ=q T \P`Z (Ca_eVO|XPErS˜SBB[B t VŦiD}ҥdn.NIiD`Fij$*'W9iʕ+QN4m^^^_ưvڰ`޼y>޿+W`ԨQЖ.BǏѵkW$$$0mhhǏqkB`…x]>}9.)) z+)-EH^5fBVx8]cWDGZթqݺ)U^=I!%>E3S$"Bx D- CV}YSz H` [I't:֗/_Ɛ!C`eeӧŋcFXh^|ϟŋR X&==dž 0rH$%%D!.] ::ymٳmi BxBKUƗ/_гgOʼnzB5w YAA5ߖ^~,N߹TI==l>ZZ,t;7oի_y'!h8w Yo+WJg9O5fǡW{`ր#+{`*F"􊎎ׯ:`߾},5Ċ ƍk׮˗X`j֬ @X~;m&J4Hvv6h"ڢdɒhҤ &M;v ##wDB[hh(:uO"l2͛7'ڬ5qU4h@~cƌ>!L ;{['ظQr AJ#rg͛I"~IWR0&BX4 Ay^W`왰ԡWq#Wvv60`XZZbx)Xy244qqDDD`ӦMV.wSuԉCBBŋ1vXXYYiӦXp!;!(ӧOûwD鉑#GRBզMܹs3f F Q GGw)9s]]䲆u+Wl.L(Б }x ;VI(%/{N{FI##5 03FfPիΝ@v6$DL+""˗/G5`oo+mmmn6m§O~tzzz>v*JH[QQQXbjժ;;;lٲ?~v;vļyo>ܾ}䘘B.] P;-[)SRB K__8y$J,)9̙0Qjœ׉DpD=slBqqBpDQ..qRrp0j&0{œ9Rrp D4镕sΡo߾PΝ ޱTNx=_qơDr?r6d,]+Wƌ3oʔ)ggg\xŋ1h 4oBHbccǏR3g( !DG ׎3󺄈B__lj; `/DZwoƆA%]]Z4$923I\]rxȑ %!ػ7t^/t Ɣg0 Zt)W.]#x f͚+++jrl"cǎvpssC¿([,֬Y\:t.EHQ3qqqܹ3޽+J}''',ZHڄ‚ _.Cʕ+" ++͍w LYK#!J)SF¶R /y'!07Ɣ>$9=D55E ּHӫT)ὯxqY, $D$NDD;.\mXU !ѧOݻ7?~)SАSBBQNbb"z;wRԨQXr( !+(-ŒjR0㋓Yt(D3UFD(&fM%%ʕH{#k3 (ɩbj\r0޾坄L#^R[PWW;vɓs=^zufqQӋHYFF뇹s"++KѣqasJG!KJJBqU8~l޼ZZZ'֪W___3/]Dz;V姍0A;G˖ *fڶ&oU(I"Z_wB"2wѧukh36[ea"4pwDcid+88w’BCC燥Kf͚1;ݻgj,GGG~{{{{lܸ.BZrr2w˗/R~¶mۘ 'U3֮]˼&!' @5JJ5Wয়}y1$%NBѫ0p =:LLD-(KUAԽ;0t(0,z%,; Fns1 c~ }-[e˖eVVΞ=7~{---xzzҾ]}ҥKҥ G5o<!.<<|'B6,Gs]|ADt 0ǎ~Æ qIRBxpuueZ͛+ӚD2e A#F($˜$GB缓E*%4) y'ɑ$ $V7|aS))}MIlY'kzEGG#::y]o^x9šyH%)xDdg+8FH>gff֭[A!ddd`4g IDAT8z(4h???RBx5z8Owuec#\Pf%_ְaŊڧ8DjeL)%2XxwkkI!%_+WoN"Yf%JD%$'3*U7~V޼ᝄ0qM@jԨݬ.OXx}M-9?/_xzzzͫy-{ܹSR_O !D2331b:tHjٳgajj*J}BIKK #F`Z֭[LR&N!CVBak L;w&Ed$$D-[NNS {Mi(+ssfSR 5cۚ  W֤I;v Shf>zqΎ\DV:|0.] }'HMMŁ |7\kTRyx"222[Qzz: l?zQQQf$%%Ȩu-eбcGBĕ#Gb޽ԯQ.^2eʈRBk׮psscV/ Y-BgaH7)eԩ,ޑݡ/Vy.U kՓѫ02Ϟ KN(pн;;IW 3\D70`V+95Yo~Qxҍ큁uYth\KGG=z@=c_{Abb51c hBĤ(t/_^BBB憿K,prrkײeгgOUTXhh(֯_S[#%%ƕ+W.ϣzGӧѷoBG`` ߿qOOO :NA'I&aݢԯ^:.]_'_>ŤÇmF"DeƏ.(_; >8? ?LUdZ;G /\C cIq+-`v`dD4ՠ~ظq#ñi&/ԟ:u -[D&MU!T9pwwСC!Ǐk׮شi\CvsB-צM|Ϙ1K,ZBBQnoMÇysqq͛R4DNA4ɜ9@ÆS~z1h҄w Y N!9Ɔjed =#Y=΀&8!ЊDD7nƍׯGAFX߿c֬Y8p Qn]&͛7G\\q}8rw^8ph޼9V%KbrC>>hݺ\GQR%̝;"'%D34jǏG~6qy4nݺزeKc6!$/...Xz(˔)sΡjժ')c"V-2!X[KoL}\)BD~UHoL} xzoN" ,^ S lg)TP$L200@~Я_? {w6 |˗/;;;;hF7m8}t :T,rl۱cG|>>>8r߿oǵQR%ԫWvvvݻ7*VoaÆVZXx1.^߮)ڴicǢ[n=?ٳ ]nmLMMq=&,HQyaٲeԶj׮-J}B%K2W7BԎ-0e v-$DSl 89"ХP?_MAKf̜+x',XqJ%0YX1fո10k_9'33'N'PvmL4 K_ω'sssM [###9#Gh7v7oǏ~!=BWWMh3!jk577lllDO!ؘid,EKz ah݁H`>Ir| _̜ 0~o * N;I@`:yR4\dggC^й3%,-(AA59@R9 caas"88GEN 8::|0a=z$s<22ٌ7/???\pϿ"DLLLd211)E!߰xbQj*U gΜA=ڻR1GM/Fx dHK)d;vYYE ;\ݺlʬ*^0hгjY{[Q+:::իΝ;/^`ԩr-ÑM6aÆŁ\Ϫ{{{tۏڄBHQ_U%KĹsh(!@R1U_!D6䝂h3}&oLJw (ggE )d8QdgʾiLhZϙ3S9PKN5kիlذA;1h o߾>&&&I#BHZ 3g?f͚RBMBBzg"9@SMT;}??)\]jxup,*z r2LQϹ <;)5 &LǏqezzz>/11_|ؓ'Od!OOOL6Mŋɓ'ѦMQB:bbG!cb"\P64䝄h~Ɣ^?//CI"r2-@@$*uS-˴ )SS>Mztۼ|PK ڵ޾} /{y"""A!5[l( q kN>}Ĵ"EpAVTޘXxwJ&@P$*üeiɴ^XY 3S$6Xc+#5(W-Zw;++ ]v'>p{)\B!(ڶmƏlǡCСC !D1DM/RdjL;$͛ӧN!`|QƍYxxxB##myԯ/Oԇ\PӋ!=== 0W^ŋ/0uT|}NNN([,6m ˗:'M!I;vرcż>|}}ѵkW !D23ʊi=B$G``)&qp BV` n; QDQS Tl,$|i===Ԑž;cN!`jzVZXz5>|+WZeee! -BڵQ~},XyoƵkXD'B47ƌ#JKOODݙ&Mj_T^i=B$oHK)&[7)dݺddNB1hг'nx' WckkgZSa}N!}a߸TIȿJ*ggg899ܹsXnN<)E'Oɓ'Xx1*V޽{W^hӦ ?ƀDYB4СCɼvލRrG!ʛTJ*3g_T=D++RX^Jl!_ׇ.h&&j1 DF 8~03 ᝄ(QS7oNiaL ; sBCqk5[֮ʹ&M49aLImvcFM/B.]ХK`ƍغu+{xzzƨX"222$ʝB!ݢ:::صk 6!hW2fy"1xۯ7_ xחY Փ,WWa7ox'ɱ?`nNՕ07c-)nTka^Si{缓+ !UTnݺ5s~^B9AƎ;0 ދ^H+YR'~* xyNBad$%y'ɑ*,p0Q!aLIiVvz:};p6$|ĮוL/TҼcqwjzqe``~wƍCCCޱ!si 4kkiiaÆ :t(ڄiôf-#DT*\_ի5[ȯR%鍩` (w++aƗ01ӦreX3L2lgm 7Wx')$4*&M`ӦMǪUPreޑ!pYOHacY---[ƍc^B4ôf#D-5o.,&%>@D$DfN!+, Xwsy#.-5g@vdn]%%{_h($E5$TRprrBpp0Ο?ݻCKKw,B!D-?{FJJ ZZZĉ&M?iM333Hq Bxpp BLxI":wFBV` v0K;;`)dyxzѼ($+;==)7ڶe^ xZDENRdQKaooǏիW3gL-!"q׮]C>}Dix)SRB4ю;35۷om)-kCo"4MaU%*0x0Уnx'!0Ӈw Y Ɖ:ؖۇ/_2[JԮXy]Q3з/?LJ❤Hoj˖-ûwaԖBk׮HLL%K0gQjB&… ׵g^7}ܡ?x{NA5u*ЪN睂(j$M)d;;Es)Jm/m_:N!ES^j&LӧOq!TZw$B!Drnܸ$$$R~6&|>J^B5gy rsjB"Sw Y>>B^ CYkϗGNQPK ikkӧO1vXq!ɸu~Gċo̙31|QjBڷoünϞ=annμ.!H\$$9RS%x'!00.(KitaLݹ; Q0,,x'ɑ!,z$z.s"Z=GvK)[gi;Il`.UIjz1CCCxyy~Bڵh iӦ᯿6!h7obܸq1b(u * $:X杄(|y鍩X`Z 0wKKI%Ã7_L y#N uIoa!L$GBq#9$E541l01!n>|{{{DGGR +V6!D3#U 7?Ç e˖E.]%$EJQ&̙S <<y'!h;w Y?"n]%l MD:*Y uvϟE;GvP\9DZr)V޿睤HXz5LLLx BTǰׯ_E?f\Rڄ kkkxyy!33wѹsgƊR6!K`H)d sDz "ƌB֛70 zh&]j XBB1%r4:̜"żERcêA@4+h%c5ֱ^{0&$73KL,b&F{FEQ"Hc%9٣&(>x+/p8;ך,]a_%^Dr0lADD.^Ν;#>>>_裏wQ6DFFbxk.X`q{CͧW\ya~6t(гtѱcK%C S.P @S59zduSVt 3/G!C0wÅ yѠZ51@_S۹X1Ν.)t8""""p k]vŖ-[xl޼'Zn_8~8233{_s=QFѣÿ{=Bqcݓ̟ܺҭ` ?|Ԙ` *Jtԭy  8wW˝;ԍZ(+ ?ݸak4<>qo+U ;z \\ EԮf,\ugLDDDvڵkuyT IDATp7ʔ)#ALxBBBpqqAӦMѢE h͚5CժUP?G_{ 6lg QAyum*WŋsmHӮZS}$]bቛt T6Iꮽ|`pz)jAmuN'}=8E==Q %]\0=iiILDt|@|?Qti899YYYHKKjݻw8TDo݁QKHDŽ jM}tjM+]B:ƎUCK잃Mɽ{K5Jkuw\5 D$&&Jg䙿xץ3  0szY!EϞ%bQC 3mmZS/$]B:]S[V_^SN1ٳΧNIp\+"@DDDDDD[>>>x3΀!]b ] 9"]B:9Z^^%6YYuj MOK-'GG,<eUJW9#]`8"""""""SpttIJeA:奆frt)pt Tk*gNd 0Pt.-]`˖ä^ŞlY5*YR&#CqEDDDDDD+͛7cҤI)DϮ ?3KHG[SDEoo_ _|Q:żj0)HTݺuc)DԾ=0~tQx8$$H6m@988`R^> *T1ٖT(pEDDDDDDb'NE<0 VRSKHG>AOtQ]2?X6e 9;K؏n݀å+(pEDDDDDDbŊ/~z49 DLk']a l,]AƎ:tx_H b8r%7n,cFP/kzQqqq.^9T\. ]@FAAgIW.__Q# O> V cuÅիֈ($dfTЋ(jժwww S+RF˗/G2e99"]A:,LdeK.??+lV`oKT6Eoo888H+^(&+c̙<+/Y,@C/"""""\۷/nݺ}aN2bŊ^éSn:T\Y: ZZt)pt Tt@r2|9pt psSkX1` yk z5 ۶Eg4U<<7t(m؀%'tRS3tiQprrBǎ"::ĬYP^=4+Vo7n`͚5xHRL 0>%]B:WC 3sGHQLbc /77C~=>.Ŋa@۶;q#5 ^nnY[j=E¡QsttDv . <<˖-CQct2doߎ̛7wMx8$$H6m+""Ԑ".Nt20etjFvFŊ'#htF-v놭b?Ag˖p,KiS`l !ݛNDDDDDT8U^&M¤ISNСCow!))I:Q+zs(^tYh(r%0mbだ gնtӧ%JHPNꥆ%6?Ϝ9(UJr{w8ǣ h Q}P 2d*S5*VDڵѡqcҤ jV$EХo& 7o͛cFHH>'ODJJpUX[FVкuk4iEcʉ`3FtH</޽%qFV.;{jDArj{j_p?\p M1q[ҨX<+_ WG5РZ509wd4tZS.]B^Kg`0dHnS֧GHЋL>>>DDF>} ߻w_ܼy񈏏GBBC$''###Vwޅ+dɒ(Z(QT)B 򂇇<==QbExyy,._YYur5O]TySDZOOm[TdtƍTX2_ԵkZ?5M7mRΝYX,Y@Xt H2eаaC4lP:(w?T֕rs]PNOQWժa$$5.]B:*WVC 3{XrEtTd?Qzح[ ǏܹsdN$""""""7nٟ-9f 11%iSuƗܼ%IG(:ZhѰ!_&$@pfϞYYYO}\%зo_L6 /RV3g%KJPNڸQ%`r5<1vy=]qqu%6aa9sek(:vTkjjWŋ@L[0Ro#g^P\xBTT>| $''۷oGƍ|>|Mk`ƍظq#zTZ5oC@ĉt ȑ=%6!!jMM8:JPN .(tѣ<ɓ>d_Vv.9yR)SEk(Wkjvg^-Z :u iiiے?O;wXf DDDDDDDc:~@1kR?.]bkZSCHi#GKl PC9?'SK٣~O)]B:&LP[4:$]^Ξ=~!G߳n:/߿`ٲeyDDDDDDDd6HW.^]hf`> Ԭ)]a-]A|}:u+l1]3~~@4h"Ϟ˘"""""""SHIVΞ.!J ʥJI<|Zt (QB]P.W.G?-tW#e_Ѣj@.]b Yc)QkBg ^ŋg˗ׯ|||~=+/XV3W\ɳ$"""""",\DDH?3IHpQJה5R~u.t)rR%wO˗KHZSNlٲǍ7L2bg>)SѬ:LnTkmѤ #]aTtt h|`| *JtԯvW.hѢO~q%̚5 %Jxc/^ 5))]4u놑#GSN(Vcٳg~-DDDDDDT\,_ܿ/]B:vF0 -KHGǎ1FWK;o']aLMH.!m'JW<8ʅ={ ==;::bѢEضm*~III k:::Ý;wW_aڵػw/_nݺ=BHn%]B: z0:rXVCg`W/ 'իY_d0 U[>|(]B:U^뱯988`Ŋ6m͛ __P>S4im۶>FDDDDDDΝ@LJ&2mТt_[HW)SV++Hĉ3 6m ]:HWjzitN>x =:[ϱrȑ#1bĈ~x !""""""6IW.YS(0ؽ[tY,@:F[*'??A mۀ> ]@F^Ǿֺuk̛7/[?…%K:w 777ٓ!""""""+V<>(.(+']b|1pt (ZT ݥKl23֙GJ"EԚcl ܣGjߐ T$]Q(qرc`8::fԩSQt?|#7onZhhh^Ȯ$$@xt RE]P6wK+WKHGJ[SIIjMIOO5(bŋ%]*ZT1J˙3g /AfͲ9z*U z_V-wet͛?pt h3G(:?_OÆjHa&oSQQ%AugĨS%N 4>իW655r9ͿlٲcbbDDDDDDDv+, X wOtt ds dOqFԄѶ-0qtQDp!?׫NjL"]Qp̽{"555ۏݺuQ\py͚5sDDDDDDDv-- X 8yRt-.(ϛEed3.!NNjZtͣGjMH.TIj֯.!]f+q襩J*lܺuڵk>|g/9 wŋ+WKHs~~FIIҥ@6L&S0atQr2|9pt pwWEKl<>8^t/.]`8Ժuk{{~g"""in/m׮*Tk>vW/KDDDDDDTP?4j2۷ *Jtx{K<.&X.!u ׯK84i{hѢy!333ۯ1x53|3w*WK;t']aN5`" >^tjL,]at|J(45n~݋M6eK,~e2d"!>>>pvvJ'N K}+NRƥIPaq b"]B:zrxC;^mut QK#FeZFFzEܹs'՚7n\]]H/} _Z*N$""""""*v+Hĉ#n^ or&z%+Hט1@ǎF96Md8ʅ3gTRaƌpwwG:uPJHJJ:;;cƌ4EFpȑǾ'Ndu򜛛t֭_JW.__((صKt4l(]a'JW.I ;ۥ+ ^PBO^ZZ._(CM'L?OԮ]q){geeg_(ϼ_5ѣ%I <=Kl=Rk*$DtY,@J6V+aCT"]aaptQ+N>}UV c_駟ZBZ[oƍ|{EtttHҀtQRt)&]B:*TPVr2|9pt pw|}EKlYL.HrԚrq.IIQgƝ=+]BLo6mB޽s\KƶmPlYGcܹst6XVV>6"""""""mfr6ܼ)]B:40ߚ"#KHGݺ[S%[ $.X.!7z|s+WKO8|mڴ֯_@--; A<<" O:ZI~+ΜQ[H޽+ΟW[>x ]B:z 0tImɚ,]B8cq!|5kիyzzwزe N>&M[Mn@DDDDDDf1i7滽{M+H@F7JW1c+6lrp ȑ@׮F!!ڵ@Vt Qr(ڶmmŝ;wPD =UBԨQ㱯)SbŊ|~TT 5jԅ IDAT@͚5DDDDDDDb,`,mgOajΞ.vw#eZS%6۷滻g,MٹS)H z'''TX+V˗q ''59sGF֬ʗg% _T۶5SQQ%665e){Ԛ.ٴINK7$""""""WR88Hܿ|:쏇,]bb2]}j"]bΌ3]}K5UtÇl.!z?I]3`| 2Rtԭk5 ,X\.]B:ͷEue_[S @xt QqGۘ?>KܻwO:ȼڵ&L0vM]P.!['KW]Tlt h6M 573͚m$* 7D8ʥ˗/{R 9s7ٳѣGxyyҩDDDDDDDԯпtљ3ʕ@jt &550iҤ,#""""""3ǫd~`& 5f +F~ _X%c(K `Z +Kt t.]atf )]BC/M5o̜)]aoK_̑0VKC/M9z|-жm|!""""""* 0:w#=ٟ=C+.^>@mI[7` 0`2=ѹ30ztѕ+jM%%I!4UP![srr€! cW^0[`zj.!F]HWkYY%cp{w #GԚ̔.!C!;^ K.45lߛ}+NRƥI+Oxxxoȑ#޾}PfM=gϞͷՊ{/ߞtQۈɎ@Pt9Smwh&l*]ANU۲@`t4 hFh^`f "z];w{iiiXf 5joooL>}ѣ\vzz:M\?]XU+6m ] PtQ` ][hV5"dKW!=0'{??~uؾ};Oʕ+@@@XbX"ʕ+WWW\rpr5qqq8|0\""""""z-.͜ P:uk 58;;ÛGF?#]B:6fϖ0y?uKt4nlA*֒Z[d'52{*2RQzijРtVF/˖w5J(,Lݝc)g^y;V(<\ݙ(]B:ڵ&L0vM)lI47ADDDDDDDt&]a꜓L1t(гtѱcyp 4G)`*u~ٟ~ԇ+Vs. ;(_<|_ ݃oމKD4-::{8gdLuֱcO瀇0dt :SL+|w7RLKݫ֔n0A7%65ePK^DDDD/_756I(wd^5\oش (_Yt¤KleHc!ŋ%6AAjMFE ϝ. RF*&ooo+VL:T)5(]Z&-Mmvt pqQ]]%#X\w5R9;~~.PT۱~t ppPKlV`z!zFp DDDDDDD4ժ f,Y\*]B:*WV$) X\wQyy5%uWܿ|pt P9;K$'}dРA3ӭ&n:\R:RՑ#GdɒB5DDFEF;Qa/Ξx9Mنߺy1C P!+QQ6nmTQxqߨ\QyrM///s;wٳ]bc ksC\^Ov Hge[hhtѯ:H80TޙCyC) WyqMQ^KKM no z>t=3xW.pd͛7ǔ)S3*117n!C]Ȧ#DDyvx~} #G۷+Ud+Փ0dyQlY'8~\[7kIsmujtԽt ' ơW.ԬYŊCZZSSrei?<ʕ+<<2==111ؿ?..]K.DDOuEkܹhԨ`o^DTxq ѣ95f^U:ODfp*Х гtܼy08p jժ%XVZSI\ t t 30W/]b.7rKz傓ԩ3g<=777,_G"jŲe0m4deek5bQk ㏁ͥk(??bckL`ZhRrUk*&FF֭֭݁kH/k,wZ jMk']C^ԠAdž^%J L<ׯ_& +ZQtҀ #''Uה3P3.=##"~UQ//{*WV9sKl-ԖOd_l=`RjזTfRCL3HN/WC njlo TJ GjMqC#^.=\3fl5k,,\=*&"""6p 0rtK`" Ly kH_fz_Fx Ĥѣ#5j,ʆƍ.Vk7@KÆ{I?*W__ߖ.sǶ^rvmugoH 5Ut 31b@ 3i.!}JW=KI.!zCHW>39Ytt .]at2lpt  jűcǰh"khӦ jժ*U ^^^miӀ-+زEtM j%]ag(]A&Nڶ0 6m ]:HW<l ]AF:w0;`zj.!#FݺIWzsڣG%d8Gʕ+E1c֭[ÇիBbb"bbbpl?ӧ /`ǎXODDDDT|}5+ݻ+Hԩ#]ae}40 >LtIWm|t3xE ;mۤ+H̙@Fv?^ ..'ND:uyxxqJJ 3gΠo߾hݺ5.\gODDDDTJP˕.IOW[?.]B:U/wwL`QZS*Hde!!%x9 ظ8tHtUHWm8 ]A|}ե+6m +QFg̚50 K/>V2LDDDDJuAL.\.!*oMݻΣ .!jM99I$'ss.^.!nnrѢ%6~?/]B:ʕS%KlRSՙqOKRԟ}KKؤV'OJq蕇֭[nݺ֭[YYY{c_OMM믿!C $=j0W$Ӱl&o@Tt _|k*&F.!uo@ ,XDDH5ͷ぀5QIb"d pt ^yd=zt"E`xQmݺݺu󭁈(tc7+W_3:IW %m[`D `B .NtjL"]at|J-ӥ+"#Հ>&Ft4m ̞-]atZSoK q9^{ YYYZQxǾ7_oDDDDDfW/ 'իY_d0 V>.!}ue&gϪ-RRKHG^?3'%{w`p KԖ|}9R(,LmG$]B&áW.bذa(}WGGǾ3fhQ2EL+Hĉ3 V}7NIh&7JWѣΝ+֭x}1U($XxHt !]at(@_'s+-ZBXbҥKuV*""""<`NdV/+HРtѶm]Wg&||t3Gqi&;vUdf50ڵ  ]Ӧ-[JW}et^? G[o͘1+ """"P|Kddw'=*]B:QC OOG5k;>Y,@J6V+~CT"]aq#ptXե+6m ] -]ae {t^&8mڴyn;#PDDDDD*URC 3IJRgI ԅ"&pr:ETѢ%6Ο.!ʩ;SK.IMUgƝ>-]B:JRJ.IKSt pqQkU&=]msxt M_^:%K)]B:Q$6X.!5koM\SZ5TBZS%re󭩻w՛Ӯ\.!aziAx 3'%%a^&""""{֡0ntQxP ]B:ڶ&N0-KHGV)Fׯ5+]B:ZPgIdzGLt hLe&7o5ut h0Vk*:ZqoţG~1ԩ&L[0`Ӷ={|׷oߞ/GDDDDT`0 U[I}պ23g+az0:^mut 6L%%%kW`H 0uwNRt 3FU`b 1Q8Iثcǎ={_VZ?~ڵm[lį9r$O_?{wU{! IDAT`n(\TMKrռ.2d{ݺR.i*Z{(\Pr Qm:̙~xs>z^gf<߇DV:$ln|y`p$ȑj́IWsNBzS{J'1;tHͩ1cGl@7tSvI'1 VsjX׽ R*wNbvڏi8H4dy*(H:YhSkM\Sc63g{m/h޼9ƎkL+W~㉉f\""""r>>@)mۤS^&%Bk&`哑l p5ejMg$۶?O'y^?6H ƍS~d. @: `K؇?I&͚5u$/^FDDDDUbE$f+W;>L@ժ)rrի,c2իK7֪FԪ%Bk:`)B+ عS:e2uJZX)_S:;;?K.mHH~Bۛ`EJKu!}IMU{<#SFjsڋY$G2/Pt4`R)$*RH'1wX8qB: Q:O)#,#C;zT: Q<*,+Kݜ }K/wެY3ԨQC(ؿ}DDDDH4Pj$!=NBzԭ.(IbS4Bvxsu``SZꂲܼ ̛p[ TTRp!%Zxsm5""P>aKt/駟7'nVKDDDDm[`hZ.s i0XUxى-9F̚ d7W{|jNK'!=7V{ɕ+'//ԜtI: tzpW~巌 lxf%RVNuj!&x5ZgΨVwH'!=w Nujɚ":T:Vd$hZQAC`ZQQjٟv퀑#ShEG襓=ϟ?/D˗CFR_d~`Z׈:|-#:T*$8Xͩl$ǠAj$!!j,$G@Ϟ)VP{}[:VXlړ^:i~?{,;&e׮]9sSz|JCDDDD$dRTd&`V哑l l,2hT:VP(8Nc~t kxukZv)H1cTKr#ٻNAyE/<<<zlƌY6n܈}O|#""""S&<(rrի O>>7<[8p@: Ԭ)BسG:e2);S^&Pt UA쓯/Рt mۤSPaK&M<ؾ}0|pdÒ۔L:o&<;ʔ)癈D"E"IRS^LgJ'!=ʖUYTZ3)$GRjN=e{ˁ'%J F| ptңhQuruNbZ>,(THͩJݿڱK'^.]NBz4hVIB:O=p^:9;;W^O|Nhh(|}}Ѻuk.]...Dݺu1}ts]oDʕϢL2h֬???>}ZW֮]zz5Zg_ӑ4H:Vx8xSO.!)"#En%;ѩ0|t (`ϫ#GJЊS+Tm #B+&Fݜvt0j(DGG#""… FBBRRRlM6UM"""""+#FFzO@.ڔ  t,B+8Xs> <M:VH$+K: 1`гt cǀ+^_dzN,[$$ӻWFrꔚS9!eM⥗^DG@DDDD$g Q#Z7OSF79( NAzMx{KڱX^:5~<кt ]ט1j ['9hN:ڵ)X_|'''Tvm3Z՜(Lt 5kS^@͚)={S^&)BkzS^&Pt ᅲNAzK mNAzLt M-[SXRV0h}駟H"1ʮ-T/.,- XTR!SS..I/W-(.()#,=]ͩP$GѢjN+',3S<|X: *VNbvSIH/ ZU:YNjE~t^6`գG1vmH_UgUK]P67yh$TR0>%VML5d`B 2R: Q'YYIH=Sh;| !K:j߸t$G@߾)NT{I'!=^{ _:֙3;I,z٘7BBB흯}_|1_KDDDDdN6Nl(4 Ocat kxU+Z;w)Hc^N{7/5 hN:ڵ)H#Sh:|-;m򀧧'~g|xgXE;#ԩS'OEDDDDP|}5ShI ||Z?J |}}߰{ԯ/B+0ضM:e2^^)6mlNAzM֭)(X#Θ>}:"""0yd1p@:u _|+f񉈈^RHQtwU[0$Gɒ_IUP$3Ϩ9UtL:a$S+J'1_ͩ`$T*,'XF!d2իKZ8p@:^yRJŋ_s(Z5jO>QQQ."""""kxx/Fr&0o-pw7ޜJJժ_F ,ZDFJ'!=*URB, ,Y;'puU7YqRS^LgJ'!=ʔQsxq$fiiSSͱ.]?k.ܺu 1g7ݻwGfаaCxxxM6K/!CGPPp ]?1hL,B%`, >^: Ѥ 0mt +WٳW?.իfT+gpҋV/#WIVtcGZG+VIH7zNu*ĥK'!=Nuj!&x5ZgΨ;IH݁ASh;ZJ'G`ы&OVdv`ׄ @V)vS^c/$Bkn_:5jЮt uS^#F:H:tXɑNBz  t,B+8X ΖNB`ыL&C:V@)H/__NZ6/2ShmܨdL&K:V` ut ktQ#Z7OSMJ R*2#9%?#yڇJ'!=yFͩr夓ef֙GH'!=bE$fYYjNt\Y:YNk!$IZ8p@: Ԭ)BسG:^i5j@b gggxzz^ /񸻫 5F,X?/VM]P6d`B 2R: QSNNIRRŋ^'d*TPNbb:{V: Q%%ǏG #88qqqHcC#::۶mÄ PF <GDDDDTp4iڨˀptңQ#` ZWfEaCy*.N: QTb"0{6+]xsu`\ &F: Q͛@tt^eddØ?>z-4m+VDQV-4l͚5CVбcGtCr 6m ";effbڵhذ!}dee?:w NV$'K'!=ڷ~[:VtPstWѣSh].(?_26mqShƪ9(h8Q:ŋ@tңys`dZ.ؙM^:-[ĉn:?~V޼y:txbQI233GsHJJҝ0p Уt #G+ $ǛozI:~\ٙ>}}Sh< ,[ܽ+x5u2ӧVt>SIxjɚ*2D:Vd$hptE/իgrrr0|pV~t )))6HFDDDDD VSh l ;x%ZwI FRd>_:juz5#6L7``* ]`uN\ #tڵů)Z(J,+WDPP-b=#lSG:V@*~}QI` }t dR|I` ut k lެ~>M 4m*B+(H>Mx{Kڱ7 bK_~ sAL2 [FF># 7|cq gQEeef6GH'!=Q eeE: _+K'1Q+sNBzLt 5kS^@͚)֭SS>)NQ SB0gΜ'>p5j"##gjժ=g͚5͓.ُv?#IN.T{\Y)''$f))js礓* EH'1KMU{1=+([VNb,]"S*R<#w՜ NBz,TI~I gyR ۇ 믿KƴiӰsNbǎ:t( ڵk-Q#H^f._NBz4lhbjB0{6ptң^=ͩk׀9sk FrS11IH㝧nƛSII@TtE/+?yJ#44/_GZtF!<<ХK4iݺuêUyf.\Xٳg[4>BoKЊ,nݒNBz 0zt huM$G6ر)bbT"1Q: Ѫ0at ??UT%Ӣ0yt KԍDIH&MiӤSh]SWH')0X޽{XtCתU {A*U:Ʋe,:fcT~Lo_ƍd oVO?W99IHaÀΝSh9-<M:VHj%0@` #C:cK}=،3ЬY\H+_jԨ hۻwoCDDDDD29Fl.2>_Fl*1CGh$[7K N6Nu+qt k$Ho6lNAzMڲΝ@@t ,^8qB{r0eʔ\x3,( OXEDDD@ZZ._W"..vp-ܺu IIIAJJ rʔ)pqqAQbETT UTAŊQZ5ԩS'W+ĉ|}՞F٧&+Kݝ\;Zt YKܑNܽ!,_xJTsj>J^^eJORdkV-yg穛7(ʕ󔷷tĢN&l׮]_{- dɒ߯_l*nDDTp%''ѣ8vN<ӧO#<<믿_g}mڴA۶m^^^ (6T?L:YBM{%pvvf l'K'!GѢZE1{tK| IDATTJӐ4M ?s$fWfTji +!:-[V%6mڔ>WYv5o< *T@ƍ1qD]Νף?5j+W"/m`N=ݽkoĔʣPkzo)Bi`2՞O]ekW``Zj%EJtңS'`0Hڷ~[:Vt -$E/,zvߏ˗/ù>N.],Wh=Rb=MNNN> O>X"^xL4 }nWw0|p?Dbbt$"zѣW^NonE/ׂɋb8Y#6L]T&!CTH~Uc2"=H/IaK IN6mdѝ]u|X4?ADDdTgFJ &`˖-qt|>pD||>_F|]l<D1C1͛ Lo)ȑL )H T[V"[;V6ݻ-9E/^x{yk؂>+VD֭-kk֬Ecӧ/RRRz߮89BTH{dg~ {%sׯP;3o\&ʽVS=^Ӧ7˗oNAz5j$ WLMMN`X„ zl—_~7nDorQeNOO: 99YjժA_]"-ʈW/_?ZNKt&.^)iSDTu &B+&F:Y}{S#y`hdC,zY7@z<22w~aֆs={9/^<ޥIDDT۷mڴAllt"#W_Nuf ?{H!K t$a۷99)zNA7DDҧ!j*/^ܦΙ3Ώ:''GĉQZ5 8-.\ȂgΜa;'^:͛1㏑"}6 hD:@`t "2 T[V"[;`k=Z"Ǣׯu=HLj#гgOc?3F777x{{cHLLx/^DhhMt:vk׮IG![9yg/Pt??Lt -U""MkKxXptdՓNAdR|]c^u|(SUt .|ٳgcҥr劮qCBBEDDD!22=z@*/l; ૯p$GrK3H'AKCJVKNbv3ߤ/ʖNw#)H"EԍDI'!GS+K'!+e#:uBHHZh{͛QX1'Ξ=k1 2eʠvhڴ)6m:h~ZlM...qr;V:JBI'!=QE AiF`SC.ɍySZQfTj*p!)\Y# T1H$mznݺ ֭[駟O}Mjbȑ(\p|rKDDhQvmXL 0g*,պ50~<`MDLy4 ;W:Y\0k{@JiHZFK'1zUH{@ժiRFlI^=usGI'!X1'''{F\\~={ q-eˢAhժڶmBlp¨^:<<#""rdKFӦMѢE _>իE8Ŋ'<==ѥK?}6b۶mضmmzܧ;v,ZjOO|=.ST IڌrgOu _uS+֬Nb,YL*%@3Gi'ٗD` $fӧ# lrFx祗cŋXCիWǨQgF…>… t(_?*TE;"""{Vn]+xѢE ԩS'On<ɭg}ݺuCnݰd۷/eXJJ a׮]y~,'6IWEݻpFYoJbULݹS:Yp:O Q*`7mQ<$,4X0n S7M#ҫat^vW^쎇:v숗_~ T" *;cǎᅬk";;;O?bԩSͪ}IHiſ\Io0ySG}mۦZ ?95xtc8uA知ޭ԰aIQۧoK'!=~[}ڷO: DDDD6TX1tsŹspy|W0` ^Y&[;Ϗ7mڴVNBzի!NmA}6 "5 UK:j" X2&?HۥS#ٸ;׌U%n/_?7n`׮]8q"ԩ#j^^^駟9 s+$G2o͖0ӊ1kZ(9uAHhaIQdf6FZHWzPA:YVj"Ev6ZHUNAE>6A;vĴiӤ# TR׿w^cʕٳ'JbI0Lعs'fK,ɳIHb"0{6#TE< J/ʕ,9c*嗚5e#u XNB") XNBzT.(jNEDH'!G ,YK'!=ʕSѹߟ9lɓػw?DDDD6駟b…h߾}2:_~AʕdCϓI''یs"0w*&M'1f*JCiS`Z/fIQ\ԕ+IH/:T:VdZI,j5!ٟvQShEG7oJ'!GÛYV ) Lڵk8t """"ݼ~z*dp[ldrrCBp̊ח }N8ӠA@)V1eZ_}ӥ}}H -ݓNB)5Ҥ=zH(0E/Xft""""tS=ŋq9Ku+qt kys/?#+#|l\n$fDeKZ?_/"@kn){6z4+)S#9pj5|8Щt zUZh3ٹO>6w6 dZ`~Ԫe˲ _0Q`Kf2kKZعS:U Pt ;S^>>@)モNAdf`&״i@&)uu7nܐBDDD[ѢE|}|L2wU S..HQE/ g+ El+F&qŋ eH'1PmNBz89󔫫t,`J $D: QS*I'1Vmݕ/rrj^&.@_EfͰzjddX3z 6`}IGt:0g $fW{" //uϤ%$̙tTjN}d6t횚Sァn Vb"0{:Oլ),nN{]$Z;w`5(W5j&Mm۶h߾=J(! `РA6-zegg#22^^^6 9u)#E>e V R_Aͩ+?,XAOv_K'1S%˕NCz%`(bb z$uUt70i0wt777o1k,˗G>}phDDD$5m|`[UIHAfX9pLkI:vLŭ S߾@)I'!=zNujK&→n5ILn) <̽{ehѢ~WHDDD$]v6͛6 n6`Fĉ@˖=< @V';6nкt ]ט1@۶)S^#G*B`H_`j 'G: 1dеtE/;vQncǎEzzt"""РA; `e\f2uJZرC: モNAz?/Bkf`&Hnild/``ɒ%hӦ \"Y:ul:^J5C.ݻ,_8!(QB]P.SWLrxȎ-ji+$GBjNU$,;[,UNa] :$Q`׮NAzL@Z) $gѥKԩS(^H,֭[HII۷qҥKرch׮<*Uqb"""25jt<*n{NC^0w-+yWDjUufd$fokK!KUԔ)ijߜzӐʕS+&O6ξw_}V7R,5mw=ʗ^xA: Yy7YT0p@899Iyׯصklق>7"":uBHHJ,)HM3g#C/fWiBnV+7CK]Pǫ̙tTjN}tOUԭ.(tD`w5Ӑ OW us̙\5djT+Sg̐NRlfޫR ~'[vqQ:uٳ 4k?}4Ǝ HRRl:w2Hm$d%K%ѶCW#ShEG7oJ'!=ڶƌNׯK'!=ZƏN ̞ `dIShũ}BtңiS`Ml25ڦTP!G+>yWFPPP>#""" 6D6PH;(`%%~}Q+PR-B+, XLIHwo5)B(-2={Hw$Gn)ΝS-Y&>u *(PkڵknݺIǰcǎ1{.L:FiLDDDy{pU2&$g`z CBOVaP6qQV޽t kHH֮NAz t$B!o$ǐ!@.)UԾcd wNQ `UTvm:tUV}oQQQXb@*"""Om܆Zj6X@st zT7Ϫ1+@)6nmNAzd&`״i@)nU*OS͛Kڶ  NAzMl)W#\jհi&*y!w9WF Gv,# 4T: =ŋqي=h X%"V*RT(}Վ58X: e2WLNzZCdWNv-t djՒN-2ڵS8S*[^iժFΝnZDD*Qc['S̟DEI'CΗcڵ &MBdddF ,$"ul=z d6lPnݤ83/y+V0vΞQ** e2 5NI'1۴IzNBzL.(8!lV5xm>/F IDATMѣI̶mSsj$ĉ/$D:?*ou#θqjNK'qްx] 9Ei,^lY;..ΝV,xݘ3gMYu9L`JuG)_Ϟ"+lרv颊+(K=3ڜVV+t.ZR^Eի4jnUC57֪FԪ%Bk:`w\Eg`<"ْԭ+!pqss.@\nP"@izjcGAXX5j0p@#66ϟ1zꅚ5k"11NB {lA%$$/xh߾ƣ 9YPPt~~ȶag?899*/Փ'Fiߜ8)_WO: Y5 c$fw\]xYY8㑑r\? \IO$\ @ypP㏟xƃiij8WW+F⢊'s=`r57NCOq?;/xT"/_V籜wr\|F+ uuus<:nv͛[;巢Eg1c?Xr0...=vU$h֬Y۷o?ҥKx>>(T BY̙t6s*~Uc|8d}Gam8nU+7-UiS7}$$rT/͛#FHк|Yͩx$^Xxڧ당[࿧NYUʍ;,0 jk}8 ˒\?T$`>)NNNxwj[o "ʭcԩ6?)b19~\IONp20/u1 ƊSQ1M>=~8yRc[3gTC K0ib=~Y]> lsTKVķOmJ'xXd$h;RS1j\lWZZHGs(usZRR#2.Z=F[-zߗ-[OIVN.lu'zt|"XnlӵkW6 ݻj)}]5 hΪڵ)-[J':t[]P kBh`)jNeoD!!ʕpј) r-4Z8v XBET`Ο?ݻIQ\rAAAO|Çqĉ|JUpnn)֬NA`AHسVHB ;S }azUP%* f̘1ϟpAHLb|%CFFF[]w#&_SA8xwЫW/KdIuA>ҧNH/6l@hdUc 4EjNiTٳIHeU1xq$fiijϸSAAz@S{[ u?!>(SF:YF7\D _+9LιG+??}o\YmRIl,L gN"Q+W`4pg,ǁF 0猋3!"\ 6}*>fφӧu'n,)x_;S9sUG6]fϞ}ߋ+… ̞ B!,,)SШQ#T´iӸqSԩ-) M47ԝڙ30}:\ۯ-^2[9x0r a2YI;Gݺ0lΟW/N"LĂ*uX 0y2\dx.O? GNa-*Jݧt'nfP o\S.>.e6""" a&}+F63n!441p@ZlIrxBᖢٽ{73gΤ[n+W+2j(Թիv>ުG\(V?wy{WXG}DΜ9]U!0c 9;+ƚ5khԨ(UƍS ǎN!$ vt'1t>M}د% JcfN_6)vQdΨQjA!I2|vQb gM_~tȜCϾ}t'ɰmO;pC[Q= 74nY1%K(Zj̙3ڧz'Yd I!ױcG>,^Yj^$,Xv%˘i?8^reA %J8gl{XaS6C2SX[ Vw aR;P 6X\mR%)] 7N!>p/TR^,X@w DGGLժU&~i'bp»Q 4zM~9ƢEM!ըQ;vB``8B BLΗÉ٘-Y,9l*?ZwHJR=՝D#wnuMN-X\stQd:A_$RSŨu'n"_ᙿp玺tQdM/aÆl21lre֮]K=(S ~!l`O:uPx[rԩ~PIжm9?#vJ>!OrJ~W^xqn+e3iYILb0`tnsFUgP|ԕ+0}:DFN"QZP681""t'&uh aL8}SzL`l ӝD?ɓB a, 55}pB:vH 7񤦦ùsx"'OѣX,lԨ9!9s{|ڵ^Kݙ6mCYt)3fpJF!4hah߾=ٳgGIÆ0dZ1HINeqKm_~qhJ%K2U}օR fqL&@B[ծ #Fԩdp&Ow1WX7V@ G ʗy!/~~w_w#);wHLNr|<^\vԴ4k?oLDEeܧ-ȓO ?ԝ$åK>5a{y)ZAy޽C{]o޺EJj*))s>6Wx SS5 6vbb dIل0Bˁ8pNצM g۶mwܷo_r׾Z|9~!9s4<BduJk׮ӇJf5 ̥m[ujZI2; ,9!U޾=yyx\Y >sݜs$̝ F\Ȝ-5|$`=ѝ-xyyQhQjV@ X|bV Hsp<'HsǠ 2@i5 ƌ|L8GӦ>eV;V!(U=K{JX8}"ᄆaniz_g)3g2N+XP[.!e7^{517oe}mʕ[C7ٳS !DVӰaC^z%ZlI5rVy^]-ܩ;I~@0?'7W.^mԈW5b|w/Kmcv@&P5oغUy S{NavuM;[KkJfMivX l\y=p+wߩk7\Bc#pˋQF!g}F\\?<ҥ s~DZc 'Y˗2d+WZj{?~\w4.a {)\JB#sxoSfƞVT͸qPV6שF ʛڵcϴiD]ˢ#h] 5/v8B;~8V>, .$99Yw,afeI2$%^Y!3g]]cdULEʥT^.oaN2~>>ڨ_>׬aƠA4VnP: pp,T9> tN5VBt'p玺!v! @r1cnJ*VT_fr L8Ńs3}@y81""t'(]Z-I|<̜ ONTE `L΄/_Cz1cԮ\gda3!!́0'%NUOi#81̓?НDrVv^|kMgt_?`  2`_HL .LtwF3f63gwޙ~o_ޑxB!2ҥK > *duGfTjxn&)p$Nsuޘ6q&@ax.pIG[xᰚ5ah)EEԩ;fxέZ_x`HZqx*_ީs%M>u Z|ĨSNryrd?Iڵ42 dʕGGӧCXlwgXaWNٞk)յkWl{c/ZȮlB!s^u5j$=ܑW^g?/k~g8yUOzssCc4Q͛(  Sԍ{4o}Na-, fVt#$3/;}`r6LhO?rNcшxevST(>'ii{t6䧟`HIѝD W?N~îF{p# :NaRZMmK}ǹ qi/UÛ78ƑM/˺u뤬IZZ .g}ZjU'Z`ƾ~:QQQ>}'NoqQӝ6ӰaCmF5\:0yB͈)Y,O=;ݾ eɶm=;KF^;.s*I<;_},SSU̠ ][wa^Hco€ 1-M]S B.0nO];b Bƺ{…}M$~v?ۓ&NzþN3өF!#^n(00sw^ʖ-;pSs"̤I(R(P Q^%fϞ=k !ȟ??/2ӧOٳ۷iٲ%NKڵՓfr>ٌ_[țcb}VTlbcaTsWC)o3 <鄙plzҥKf"kq+Vxoݺŵk2Ojjj^ϊ!DE={6ϟgĉ-Zԩs^t_|8в%;S`s7nth ///~ Sst'xxu)ED׮N"lTlYFth|AK5lCNaYuM9gThR. o>p”7G>wNm_مp욘X@@jf׮]3n8sJ>S_mBc̘1>}w}0ӧѣׄSm~IZ$u;5~SW7{y~IJIѝDأsghNw k q­ӣePС^GEϥp?m۪{; tn o :aNT%Y0˲ݐk׮MX"r.???C)^8^^^# cX7oײgNF(S 6ȹs8piYz*ׯ[n6#YIΜ9yݻ7}[Flٲ>f+%1B=]OdشI56g-[94F`޼LߠD⡶nUWvQdΠAҞ=dؾOI rNcC]/vTהN7yuuڹSw ?@O>37X}9~6)#W SԠA UɄIdM/ooo.\H*]vq˗gTT׿Ŝ9s ʦBdR2eعs'3gd6ͬ &ТE z)n,8Xm~jysI觓'~2y H$kjPcӍ"sƍSNԝ$CH^~YwanM5)"PсBBCf;(2gXI;I}CI=FRY}y>S~%ȾJ]S]8rۯ3f̐ /ݿ!5c C6@={6iϞ=4/Bar^^^ 6ܹs>~ZZ$==˕K-(;ᚳ۪|}_e 8KkӹIcBKMUetQd6"-MceGl>ތtA#X`rؽۈфPV'U.0~<)/qT3>3שFeM5jo!6ϟg=M .L- gȑbaņ+y|qB߉h"nLK\fAx$Vr%"##wΜ>ܘ@"`l ӝDأHl2]7n';Af1tL/?Nb$}ʉzm >_έNeOkSe>)3jYV߿?٤ϟos}dϞy֭KʕO>[n>Bx5ksNoIf~<ʨŋ0e DGN&8<·QL'Vhcǝ4iթ`!z50ۦO?ҥ/&^!z{dH<[Vuw$:Kh*$'‹>k`YcTä$G3'ה;Zb=M'dT|ɕ#齃b/cFɄ'a|yb`k"-[+t'p*=z4\.O8_@߸aX`lxmUS'M/!<ԁx <<*U,Kzxbz-J(a؉ͮP;|O:nqܩDtȜ_W;wNa.(X|{nz٣~ h"szT)3߿-S'f#Q\w q#]R^6{pO#FrfiNʊL1zbhիQ+%y&&MA\r塯/iԨ[ly))GƲ`d ݻw[fj#fذa|͛oɞ={25?~#uF||]s!jժexgϞ5t<||`8YНDxtSsj?ם"Ţʙ}$ƵNZeS6PVoѝBX:`kךT(r.Uw?fiժTX@=z4<Ξ=˷~Kxx8'Oȑ#9sŶgAÞ={ȕ+գqtޝeoq.^ݻoIOOx1114nܘuҴiS/NPP5H"VMMM믿ŋ9s/f^n۶mUV<*T"EТE Bᨲe˒={vN?ސqDRZ1S3UbEi'HLyN7+P >ec IJ 5U4H20 Uϸ@QCG\}jHHHНF}[ ;֌%ݺeX k`AnnO6'OdOq%''sn߾]^7ofvex7ocvA Bg" )o(BA?ׯ6b НZDZRQ"#atY!Oz0tΝS!6Vw,-3yz$~]1 .2vj*736 3N9s`hȝ[wa_T-ӝ$CX:ې74!n0l,"")1c ~g&(QK"CjYe5klH!7Չݻu'ɰcGѯ$SڥQtȜ}ՂdسGݧ//i11؛Q7jd @kj~&I2,] C};|fL6B!u'x);Vw k2a;w_z8 7'^B!"&&˛7ac qWЩ֎U%Ēt'aRьrvQdNp:sFw VAм$e-|߿~|B!WN7>u$BB (|=ܺݻ3 i׭SW^ѝDB!o6lz XIIѝFySm,U>%Krɒ.f> Ϗf5k3X2KdU`AQCwaYiݾʇ,<;G/4|zϸ(5U9 4Fx>>0~bcuQ`2uMtX,Lt©M<敞~)n# B!<ƍILL4l\w kÔ);3Ϩtfr"L .Nntǖ}i3y)^|}Ĩ}.NR^q_[?]S/k9IɦB! o5t{x6mkW)?J޼;GVнC=l2y6-Z@^SX S')t'q[o/ZD:eTfЯs;$<f4OEa矇tN;;3L^g>n瞃F` vHdz8IIɦB!׿Ezzc6iȔ}ޭX,{6*d>sGwaݡuk)<1Nv?-[ LԪT)s/ڶ5CU=SUlz !BÇb~~~ԕB1cT'3ٰ֭ӝBkHUKw k7nBÆ~NnN(b]GjԤ ^^^Ð!а9}\S7qc)N؜ʒoߦK6[׭K92д8d.XBw aB%B0`᧼Zlc a`(YRw k+WwN! eNaҴ…B)Y[NRrI}_gax80WYZw ajU)_N1M¯aaN /O:e>_;0B!pS&LlJD֓/ZəSw ɪ,ѣ{Mܹu'p** IDATo{ſ|t'ɐ$n0`t~8r)(W7T`앖˖{d˦…u'ɐFݫ;{orV唱T)Zթoŋ;e^X,N"LD6B!Ж-[4iK/>6+_^-(ɕ+0mj-O2滦` НDأdIl&0{6>;)Y,̚ŲoqctŊ~]]SNN"Qu'ɐɓIko'ӹ3V@uM9m~%%^ǏN"LB6B!́ԩe ^{5|}} Wԭz0y2N"Qe&/k%I=~Z"4(uMEEY}y70zB.k Wڝ;1m6mru*WI R6bb`pAwa*UwM]mz۶ԩDfa/gNdPKVhbc4qlz !´ܹC~8o/C楗^͛-[6x ![CSX{hf+NR$$N"Ѭ;p5K[nz`\Bەb'u #:*_ڴ]NKLԝDh$^B!0.\HJxwb,RSSywر#I6BY>>>{N[a󳺑#Ui:3ٸ֭ӝBk0xXv]֬ѝBk仙lW?[L *?ƌaӁX,t۷x1 㬓?;z{3{P>p 4nl옎ڱVҝBثx\:W۵ ~C.:u ݺѾaCc+_|ի4 ?#>&zDD!W^e͚5YlٲQfM{9֭K:u(i IIIرM6i&b`A *Ă \>h\Q", Q} ѝFتY3ضt$NYԆJ0iTݧ-ҝP 7ovO ŊQD *,I%XO+Fqh89oO[x8lݚ-Zvƍa@u,"#a4Ӡ ~֘Źsjk r7oÇ@%RrRwcE p4畄EFoO˩S8{ѿZթûF?Y ӧ;#ΟW&TpB!GKOO_~UX1ԩCթT+VbŊq"͛7 ѣi !Bx8___V\ɘ1ctG¹jքѣuS'ͷuL .N"QZPn!{lۣ&L 6UZP6X6 "#u'(_|ի0cNhՊ&Q8~=ʔ1穸8uMEDN"@6B!ooajR=E ѥKɦB!?Ahh(M6Eڴѝ/2*)){tNa-4T]SoN"Ѿ)kЀ gtGɼ6mK)?J޼;Gн.^ԝm<6>7%CЫTIdK!ȑ'm64tzLmիu85ҝpON!Q`AM7F5ӝ`N"ѫڨn#@ ɓ\8׽P5aRHM՝DD6B!ŋSXKHPt'(\X-4mb"̛'ON")__NB@~fnծ/gAYQS 9sN!) .Gu'PT@F ~7=5,=%Jd9g|t'ɐ$AY B}|g\|/ݻL5[.} 8ooyQzIPc&.Ґ]UjԘ:ΟםDأR%ܧr 4>^ټhRF&jf DQ36Z w L{-65 ,ΧKBLlߞ}\9rhN|*Ub֐!D]1cPH+YהxpӺȦB!B9h۶-+V 66~cO29s[nT;Ν2sf=M-ZT.?n`7lV ˔D\*lR%tv{mtoB_=NoL0>/穏>N̜cǢmӦh۴) Dzͱ쎏˗WBӢn*uUu$mjl$ӧKA3(ծ]O>$|I@||<֭[7_~ݻ;ŋGӦMѮ];t ]tA"0MQyz$V+ÂE-65X ^]h0P^V;Ú5ҦzJ; zNB&^xAV nFE, GSRp$) RRp"5Gqlz:^8 ,BBBPLX\9 CԊTAԎDuQҡmv/}Qof$˗E?rŊo-7ވ;;C'NDj*8~4RΝ/-U,W!*-+zDjV*V*Q0-g8\))~ *,zQWT)n[}ѣػw/<#G->>O?\jժjժVШQ#4n 6dHCh)FNN#9sNC&1cgΕοӐh9O=a^;ÂҦxr?O}?nviS.i'!X""""""d MS-^ ,]LhFieKv_~)* NF[k[XH;zYSح\ ,\L=4оv UxqZcыBBd!յ8XY^; jNa7ovv 25~Z;7Nax1lv rE/"""""\ҡ!=x`^$d""B:KN␑!kڥL//m\9$.fɔO|ʔp$/KڶM; (YRTJI23e͛PyZU;Cv5X""""""ZFGu IN&OMSIDzҡHRSSC:u]F~Q`PvI:sF; h>\;2i$\2^vsL t额nF` 'G; 0U;ݦMҦ~ݵS +ID>@Ϟ)~U֍|Y; [;Νf*/NSǢ;-Zh3Qp3hR;ݗ_Kh SF[k+OSgS;݊…)3i[ X@;6 Q;ݚ5)<<jNa7o)Tt4Pv  NA(v ~L4hONAbbƍS-^ ,[L͚iSϵSi,zy!%Kj'qȐvNB&åM-Egv$dlY NpLmv2QJ8df~lެLJZU;CvLǺqv2Ԩ뵓Y,zyA='IDTtT`T!$dNkSgӦh'!5k{_Z0s&LT&b-}<}ID*r!QXvtw{Itv!""""" pw .!88uJ; h =Z;ѣ ɓIcj;~4 8qB; h\:ɉr:vL; h$TR|JLNB&5 >$E^DDDDDD _;]l\ML<0pv 8୷w >;if̐Q:|2D;ݡC2$dC`pvSOk'!m#Fh;rDfeHNNREDDDDDA.]Sm̝+P0@_e휬,$d)-[ٳ+WGzNa믲nID^@)v-32=}S#^`ыĘ12X YXX;=Z; $_}|v 25rLHV.NAyhN;ݪU)԰a@ǎ)֬>X;<I;ݺuy8EDDDDDd*&SG;݂k S11@Tv oNAƏ4Na'_k S11@),"=hY3vD^xhB;gɍ^DDDDDDålY$/ʴ<۷k'!eJ NpLImv2QJ8dfԙ[h'!RN␕%m꧟iSիk'q,6zz$d*&UK;ݼyڵ) ="*J$i ;:uM9L)ISIDFצI#GMSI -A<]hۿ9S~ӥ 0hv 8L{/0dv Cdd3ID)6uv2Ѷ-0bv #GHLi.!A Ni')X""""""NaϲIVv2ѿ?Эv -[d+WGzNaY~^޽SmsIG9Wݻwߕ(t&IlLɚaыȟFW_|L yv +ئ3i[ cdj0HX@;2DFu뀹s9B4HF9s$ ^DDDDDDDEi[P:)8EG˺dB)~QpsR#|ŋKSqd=@gr4f вv /s ^DDDDD$$D;Cٲҡ\v˗em۴ҥC9"B;CfLsev2DFj'qʒXI;  TI,KF_Lji7djxn]v,S/X""""""uՁ`tA$dV- $ii#IDҦ邋e}Hy N␞.k1j'!r*]Z;CFݻⴲe8\GHyƢQ~iRQ $ǎ''Nh'!-ZtO SML=L4j$ʁ)yKHNB&6L k'!QQޗ L*QEDDDDDtI\0sҡs!)P^t !KMNB&ڵyF;]|)ئӝw#GjKHbSIDV)6nNX""""""oݺiۼYFdfj'!> 衝 ޽^Sm.=]L)@{7pv 2խ| $2%kzv2)NNX""""""*#Gmh`"djm[T . h SC@evʋAd} ٸ;,$dbkWG,zY",\XL9f:d*&F $K_|L'˘1k[XX;5J;BE/""""`tv2Qt(GDh'qf*, ?Nbliv 2"յ8}v2 Ԭ Nv T N11@Tv Ѷm)DZGu IKfNB&W$IDdP$o9|*U%\Sxe8\(n߮L-+m*<\; ^DDDDDy`D V-Z/q`OfR$IIr:zT; h8 NIJHNB&4f >^; Tj*0mpv2QN)^DDDDD*.NFRi'!:k;xP:jΜNB&:v Naw0utRixv@rv2qȑ)N&Mb VZGkKL}Rv2qn-ȺL|zNam0kpv2ѻ7Ыv e=GNa{7@Fv2ѭпv 3ӵ NaL}v2ѥ 0hv 2ĢQ`"djm[vVL t蠝٦ِ!=h[7O;4Y;݆ ܹei'!J+3@Vv2ѿ)EDDDDT,ZXLi[X\;4Nax1)Tt4мv >>T;;V $Kʹ2a +OS#eZV *,zW4[j'!aaҡ\v,:y&$dX1)V!'#ず5S8X$d*&SG;݂2:SL nBEOixA g35&;'mj~$djUiSIeݜX$dR%iS%Kj'q}W.mlY$/ʚq;vh'!eʅDI._RmNB&J6ĢQar80aKY+|%%'Gj'!^:uJTBv2Ѡ\HRRɓx$d"*J:ԩIDݺצΜONB&j S^DDDDDi5aôS:$IDv3hLb;}V;]B0iLn .1Q.$JJNB&ZƌNaw\qv2Ѣ0nv ^DDDDDѶmו+ID@^)o.]NB&zNak{2=݁~SLLt n~`Ly$dK` vqq2utZv2q!) +el NÇk[X@;:vki SO> tn`\NaYYID@n)l23GzNAnEDDDDT-Z,_L4inb/Shysv}|v 25v,p).,NAFZNaWlSlHMv|#)8m\`ы0ʒ7mNB&EUN␓|I@͚), ?X^; Na7>fv 2 ԫn`*d*&__;…)TL Шv rE/""""9Yb~$djU:4T;Cz:;@lv2Qt(,iS{h'!ҦʔNpcv2Q/e`,Y7OҦ""8dfgt|BH$t 'GƍC9$%IJLNB&6 6 Lk'!7$ʁi`Ta$dn]P$gӧj'!jy*-M.NNB&W6¢QQqtj'!O?.><HINB&xYv RJNNB&ZFNaL z >-[ch;vL.8yR; h7N;ҦNB&6 8ہ.]NB&~G;ݮ]2XFv2ѽ;Яv ~[Ѥӵ+0`v M?Lt ..1CFP^``vӦhB >;Æi?EDDDDTԬ^-Sp:vkk SO> tn`\Naq#0gLu( 衝n6Y7e$dwoW/%Ke˴Shysv~ |v 25v,pm).sC;ݲe)smh`"djm[vVL t蠝cы(>P| ԨddITL Pv 5kS^=v H2~}v +Vh S11@F)//NAbbd/RâQQ.kj'!U%Jh'qpx`$dbE`xL$/ʚq;vh'!y|y$/g4b|J6!3S9ܺU;  "#8dei6i'!!!ҦUNRdEDDDDT%%'IDÆHIx$d⦛M> L6֕bj IMONB&jՒ@r,0cpv2QF൩sM߯LT*BCI,zu@Jv2qjKHL֭QS%&&HO˖W 9vLɓIm/qiSǏk'!͚E=L4nxm`ы]22݁}S#S^Lt D;1hV;ʕE)@)V,NAG;E9X0wСԮn|IH)&WO;}LEGk[XB;6Nah|v 25~<Фv ŋ/NA͵S ,z݅ Ӟ=IDŊQSveZ]r夣\9$.fL)#mbE$WȔt[j'!%JHJ$YY6i'!ŊIVM;CNLI@͚) =z#IM7Ir II6Lԫ'$5:8tH; ];ٳIDצΝ6v2QP$rqZlv2Q%j,zsGH"9Y; hF $ @Rv2qW 9vLɓImZqiSǏk'!͚^+) 88zT; h8pTbv2ѠA]VȰEDDDDD#W^L< )~8^; ~'SH4$dS'``vӧ/ >;Æi;tHFj'!O?.>^.NKINB&xY^DDDDD~ei'!O{?nvY%$dᇁ>}SLt망P I!! [TuꤝLt$${j'qKKYrOkvҹpUwHk,_(<)z*mNr%PTO;=$-[l.ҦxByAR矵8]+mœQ8Pw#S4]DDT[nw؁͛+&""opw""""""r[;E@DDDDDDDDDDDDX""""""""""""ǢPDDD-44իWTZ'"r.$D;QPbыo'NЎADei' """""" Jސ^DDDDDDDDDDDDX""""""""""""5 7-[j "ΝSKRB$IJR67NoHDDDDDDDDDDDDAE/"""""""""""" z,zQcы^DDDDDDDDDDDDX""""""""""""Ǣ^7o)>K,K;  _nۖ-!+gΨ+RSg幱,re)ŕ+=Ϟul "BQٶͿiiunq"#|Y_v @T?k+W+u++^#) XXؽطھV2@˖@vw:xWKO5 ѯ?Qlsp"p̕+u/ɑ8Ko67J{` 23QH/"""""MHv%[nڷ:u:wJNEqHqh0kyR+? 4mz99ԩJ( ,\h(Wr aaw]}uj'*8? ,]jX"?EDDDDD ׵*TF4NC Y``tO;&M#ٷ+_8}Z:"^W g`X3?tcY"?EDDDDDG9y[!X1U+vmvp0p:x5o IDATY? ݻh`N !ǰiWΔ, 4kvLCZ<HIcceJ <42k>ٰh??]>*S8!]];[䟗_D}{X"?EDDDDDykx10c}AqOT/Zv]f,^mX:AתYS^cxbSѣc+~]P6ަ}{`{w <>/_u> GZcܱ,_›JdVW֝;v.Y-S0֮^yE.pE/"^DDDDD'O=$x㣏}?ŋ^\hb鄧).h,z…siS̏o H7z,1?&;Zx}xncl*⡡Cߖi l` `<۱ED`ы))I9}vŋKtic][oy6,Lցjx^y?]Ϣ˒TZ!!qK/I~x1瑩__:Kl,pW^8/J˛QO> ̚Udnp>n.<T vy)x@ٲ̙z̔<(x$rrd7p^*UJ}utv;AQ#GpljyXR.͏L*TDD^`ы˛Nnz xM۝<)W`STFaYRz}(!kp?ǏiIǎu]No_d "աwըcwra'11jQ^DDDDD_tJGy>)p +2m+|t钿BB=jDDժy]dת0a231]QƑE/""""" ~!!)9O/LZrrG 5k _|_ . ] 﨣75ڵM;.Qcы =od ڵe-ğJUOHԙoN-LŊs渞ȑCDx ~}sg`24G{Go" ¢y1`ϢeZ̞z 8w'e\\+Ӧ9/!`wOM bEg!" ,zQq}ޭysgѲzv Vs+Wukws:vG|".pjT"иRS?Q`ы  *v;w G?ŋ@Lm &;/pN.D$-.]NQ5hvǎo" .5jxަNx뵘ܙ:}i:@nǕΝ[o/5H… …AD$X"""""¥re$%v Fof@Xt!cؿ !*ʎZR!,̻ʔDDA">Q*mr`>Μq#Lo-k:YiӀ,Yn!s p,dfv F̘~Zf &7J:u,DEՉ]\PRRy?GA9yx,M&W]cы] ̙hHMPj D!?Qzm€{q.]> XP:*ϟin[ÆI?\$?myΝGVI+w$+%JNSAu4_+5S/Xz6+K1{G M@TTgrt8_IO8r.۾\ܖ-rۺU:rG*z9#=+T5FNuodek>7˖-As^JJ JZ*?Ęw_̝+M<)[nG9_;+Kn?&#Cs/K\?eɱ}ֻ K [F}<4\"9s:;W\tiM) bN`t/{ 4l(ˀ;//y1ŋK9/תLGӦKz^."B>O ~|oj7}j>劏f̐zGx޾V-,Яй}Je x!/""""""|5iZ:S'oӽtZHO^xG'P駁G:Wqm7gؿ_%xWm&#~ܭg<1w.0bw&LmsٹSK<)R^m2``yyZN0{LG'^xAFe)~7k4^"&HqWw|!Рdns}3s옌8{,[A[xۥ潘 yeK#}y5˒"ʸqލvL)sW(QkgJNƏs­+M ^DDDDD_ZE}dOW(%KQd;iBj'dR:p@?k^_xK:-ҀÇ_~qbŀ[Fuڑke4D^DUKggVANޭ6n4+R=[: MRjVF awm/ =k浝3gYѕ+tLH 7:A)Δ//={w7n, oք_)n~d輸8amwuL`,ٗՓ%nv:߬,ߝ[-,kڻײRS-%JL%K,kWϙg9'mzW^of<*]Fs\?vF˪]u-eYVޮWc,;ֲBB{+]ڲ~-dI?[6I%'= '"W{%>52ɓ}>㏖U/^ܲzʲv1YYzeOaY[z>f?#߹ӲV b,k,~w._ߗ28IӊLǸxѲOwzRŲ?~ͲjԸ!!5`eΖ`v3-kYk=_y?eI1hв>Pk;fY/hYJ׿ZVNY9;ҤerۻײRR8zԲ>̲zsmʲ\=ו+u]}Y֫ZVFefZ\g?WU5ye9sRS-k4\YZR2珩Yxa}з-[ukIU/qPd6~ݻ}˒k{]}V=lMk3ҥiI]u#""""BGz}ez>ܹ{diTCWs}fv} Ov\ j_{4+;[F\zZвbc-)JOxZJF+}`L=zh'7QZ}|+9YF-gYrpսee*Vt g9{VTɻ,'OZ։^եeedF42,|W-#W9ڴIzVFxx]27(Zs,kx uݏ??ƫmۑ^չe *Wߞx²yIJuA5kzR<Сd;2RF b@?K&y!#rr,ǝ}ƍېs}Ϝ9{7@?ܷL%yy__>~פIgDDDDDTT++˲|S:p=ϗBL}\9ٖլ۷Ѳ ;n׷"UFNߟ׸qdjt=xPڬvV%W_yy iۿ쏢eYV^cRc-Q|]w_uؤ,qqBɓyuӦ{sVʽU:ǹsuM?&,̲vkYRwVȽM>Ms;} y=X[r`AOvL|mbŤM9f맞p:[1nY#ITQQ_Sڦ,G^1c=Vdӕ_ΞmÇNYrն-`y&3G﫯d:WS? q6ՕOM\0v߯T 2lcIL&Mn?S4תV)BCB't/GM-ޠor(,Ybz03>ի;iiYP__=)932}'>,k&4i)#]ȜFɚt0aT̳Vd A2~}:wvMﳠuzu|MʮvZ[ 3xqۭ\iWA> \ߤW(Yz 1#UT)|;wWYl UL 5,/, h='<Ι3RU˅ bZ'(6yAX1+Ov\]弰4twYgʗ5\6ͻ  kʟGK|0j5k^ٱc=嗝׭pfyBBbW>77+HWNVΕΐ^ƍZÆղSG-H'?{YW UWۼY:]1MWm'O:=[Ώ?eey^\9onKM79&$}Kq͓P;2j܏<իck=ިRhy<47߷}^˛,K˨]w^}U.rhBFT.شIt9'-Za_~VZS&Vҙ yƁƛb Eߞ 7%seȫ]zՒ%}ɑ#z!!Wkp*bVb"z>P:_+;[7ɋs]o{HHp]ݹ}|hSi~iOvъGtFJ2{w}fΦM,zNoHDDDDD̳uaa@F2ާ IDAT2(Ym;'J{< g?|Y%11rPXT)44L]]OQp钌p_~\˛u_I_hq7/ zg)*deA/]ߗs}m?{>z)Nbaы{w]UuB0 DhQDD<%BE}P ڊRUh>dh@ & S@ 㷲2{>{o.|?kE=g}=So&""""ozHR?/~=0iRkX=5r^'6t/ |+0,z8S󟻟ͩnn ȟ?< E ffze`[#*۴q֭y-^l=VMys*37{:J8x]^,] eM3fWoT0xM`~%%˫KIo=~~TsmU+ ED{|f) """"""rRϤaazM7X)66nwo>s.eecD? n w)!rTO? wr$8tȷv68_)Ds?۷޽HII~ ̜_OwCwM/;ŨQ5./X4l(ϣJ;keב#%Á7h ? կo#ysguҸi"""""Ką _{Z`Qx<%818uU  M7l*+v}}sK7J|<^Z&N4TYlqK#o 2୷>}~gjIt.(ۓ'_m/^yyՊ =z7}€%A/""""""Skݻ?|ue믾,F"(Uun$^+&-=s)4iʕ{~%dh]N59mnE7OٳN=]i?iiP)`""""""frWׯaТ~ڽ)t=do.vr11@RRBצ~d׃M͛-$ave.F/~XV[O}ohܪUeǵʨcǀU$_O)I+)5g۶~eN~Ǡ'۷u߻v`B ,, X2GP<h@Ւ>ؿeK$j?ޔ;R#fH[zc_mlw2c}Lڴɝr;]J={ԅ- n헯;oRSݻj?,Yx׿ii/]hYlz".Ngsz_~EA/"""""ʬ.eb8[7e**>L}?bce R79*JJ,=cpz%'{o$ lR鮻̖o/h2CQ@ˬZ>UTX|y=x~ 11{RMYopu Qu;wOLtVny9' ;= ѯKLƏS܆ 2+ML3Ѩh|U =/rs<8z/%k֟GOGد~^ˁck"5U2S7Zq1oW)2X>*ll**wLxiϿrϿ7o| R_"k""""""w>_tA˭|iSzܹ2squIݒ7x1С~Sgw }AyMet}+4n Wӧݓ'ufffe[~(s)/`W1OF3 $(Q)2~u )S̶o*/ny'rUp<:{6;2fgJzݹ8rٺ_}߽;=.nn-[̚%cƸ[6A/"""""rI`;(/8wYQXhɓϽ{|{w;BgL 3|?.J|/ɜYƴ"i뫯Gå]xQW$b <^Aߪ*Х))k>)XLG:6Cgy=<ϗԹS<`um:wf㏁ɓ}o~|69{0GPʯP֭>utJIiǒ%fi`R_Z/oo۰C-W v>:TƴiiB8nz;/[w}Zcy}\Fpk^#@^[SSec}k7Xs..y X9=ON ^?l6z6o7}JtxCt|{ET9E (y(ݬ,z;=v?WU+|`ڝfd(5rdmiw^~AR͚YDRRe9u5ʺ^xAӧ}ի߉s;vҳR.jv6h_ɚ5J<}~{+ӓwߵu׉26.7R[ltR'O:K̚evlJ)j{~QmJ .ٳJmڤT||{}T?ryԼT5Y| w~*թپ&Vj,_nާ)zZ/>zYsaaJf{vmbb:|X^i|7&W+l^BNVYN+DDDDDt+?4kFV yzHiI2?f7JCmJK̔F+a?I Sj6TxCk\^uwoS)uRGJYO<9Ҧ,oQ_^zImZFDG+ηQdd(d}+u= +yR)ըcQ>?~&)r>ٸQ.zIIJkϢ:T3ZHG_;j¬Q/))JkgV3g~mJJPn uРnTZ:UWNfVo%Θ1_CO=ԁJ;'Ad lYs'pY-7WȈWdR?/:_9瘼:w~)rz@KV粊%v]u͛]iiխQ#V_~WJ=R_~=3x,d @V@orNth޼j>}:lu_Ujy̔g>}>U׏YڵfY盯x@y:atF=yR;tȑ=O_)TGQ9rD~> |_k TxfGeKIҽkնe(˵l)nV%)JL |Y#0c<1+}KWwe p-e?_Ni#scΫWۛsgסwoڵ.WR"s:%CIhOI ٱh| x%Igz5m*t5HZ¦M<8wNy9{VR</@k Tӧ (/\R(z2qK.#C-ɹn[Mw  cl_Uwꔜ{ii*jj{Y4Pʹز#T&%eDD5sgaCI{ x4ol&*}2OnY%ArjMΡv\ׯ7?6%`^rv*LJΪU6I:[O+rsÇ%\judnI4c=) *9v:t{gͫi壏$}'J5o3//<\ΙD筸XRR<#5yjX$%E1u4]V=O^ٺv~s_hB%Z3_NK1N,Zdw1C@Rܲo__:kӎG.…ԛ1c$WNI{@f̰==]RrIQܤ pr?={Acn8QӓޗWbb>MխR[,;/<]\e zik5{}lq{K~nsxIKsw;sQjR#FM7IJYVz{U/<謌6m2}{挳}6guIMkB?A +1^zM7ϓW^ 7FUƌ1HIV_,]V^w;ifYa];3iM_11CuG vR{Mlե9QQU2j>d85;&5ĩSJ wANGRp5"""""ڮx} kKH  ]vxL~D2g(r""BFpC}U);{/\6LFsmV5B;%ˁSZ[(C74i"rwF&_1!ٳx1{' ѣ@bp͚I:8`͒[} RXp! bH',L.=oAr ̽40V6zHR오$(kmyӯ0aBggqˈKlI%f\ٌF{XpKAo%ǖizN$7yr"I׶+_:ȿKG˗~jyL,X,] n]f͒FJ'Tge$%N4=& n5 /~$nHJ~|s瞓{ɹߠ/1unHNԽv]Tr;ް8sr[KZ8O6 Ν+iC:uuNk["#_wnZ<._.)jg I8{omD+_w3y ׫s;qUQR)r2~2曒NJttvy)q*3Sow3G""""""),Q[))ȟ/#;7?8JNbbeN '.\[ʨ'aQ#"!A:2DpKE|Ud\x )Kk~i^>҉íFWV={{ qiime2 }} - q2-types >={{ q2_types }} - pytest imports: - q2_diversity_lib - qiime2.plugins.diversity_lib commands: - py.test --pyargs q2_diversity_lib about: home: https://qiime2.org license: BSD-3-Clause license_family: BSD q2-diversity-lib-2024.5.0/q2_diversity_lib/000077500000000000000000000000001462552567500203455ustar00rootroot00000000000000q2-diversity-lib-2024.5.0/q2_diversity_lib/__init__.py000066400000000000000000000006731462552567500224640ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from ._version import get_versions __version__ = get_versions()['version'] del get_versions q2-diversity-lib-2024.5.0/q2_diversity_lib/_util.py000066400000000000000000000113431462552567500220350ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from inspect import signature from os import environ import subprocess import numpy as np from decorator import decorator import psutil import biom from q2_types.feature_table import BIOMV210Format def _drop_undefined_samples(table, minimum_nonzero_elements): def f(v, i, m): return (v > 0).sum() >= minimum_nonzero_elements return table.filter(f, inplace=False) def _partition(table, block_size=100): number_of_splits = max(1, np.ceil(len(table.ids()) / block_size)) splits = np.array_split(table.ids(), number_of_splits) split_map = {} for idx, split in enumerate(splits): for id_ in split: split_map[id_] = idx def part_f(i, m): return split_map[i] for _, block in table.partition(part_f): yield block @decorator def _validate_tables(wrapped_function, *args, **kwargs): bound_arguments = signature(wrapped_function).bind(*args, **kwargs) table = bound_arguments.arguments.get('table') if table is None: table = bound_arguments.arguments.get('tables') if table is None: raise TypeError("The wrapped function is missing argument 'table' or " "'tables'") if not isinstance(table, (tuple, list, set)): table = [table] for tab in table: if isinstance(tab, BIOMV210Format): tab = str(tab) tab_obj = biom.load_table(tab) elif isinstance(tab, biom.Table): tab_obj = tab else: raise ValueError("Invalid view type: table passed as " f"{type(tab)}") if tab_obj.is_empty(): raise ValueError("The provided table is empty") if np.isnan(tab_obj.matrix_data.data).sum() > 0: raise ValueError("The provided table contains NaN") if (tab_obj.matrix_data.data < 0).sum() > 0: raise ValueError("The provided table contains negative values") return wrapped_function(*args, **kwargs) @decorator def _validate_requested_cpus(wrapped_function, *args, **kwargs): bound_arguments = signature(wrapped_function).bind(*args, **kwargs) bound_arguments.apply_defaults() b_a_arguments = bound_arguments.arguments if 'n_jobs' in b_a_arguments and 'threads' in b_a_arguments: raise TypeError("Duplicate parameters: The _validate_requested_cpus " "decorator may not be applied to callables with both " "'n_jobs' and 'threads' parameters. Do you really need" " both?") elif 'n_jobs' in b_a_arguments: param_name = 'n_jobs' elif 'threads' in b_a_arguments: param_name = 'threads' else: raise TypeError("The _validate_requested_cpus decorator may not be" " applied to callables without an 'n_jobs' or " "'threads' parameter.") # If `Process.cpu_affinity` unavailable on system, fall back # https://psutil.readthedocs.io/en/latest/index.html#psutil.cpu_count try: cpus_available = len(psutil.Process().cpu_affinity()) except AttributeError: cpus_available = psutil.cpu_count(logical=False) cpus_requested = b_a_arguments[param_name] if cpus_requested == 0: # mutate bound_arguments.arguments 'auto' to the requested # of cpus... b_a_arguments[param_name] = cpus_available # ...and update cpus requested to prevent TypeError cpus_requested = cpus_available if cpus_requested > cpus_available: raise ValueError(f"The value passed to '{param_name}' cannot exceed " f"the number of processors ({cpus_available}) " "available to the system.") return wrapped_function(*bound_arguments.args, **bound_arguments.kwargs) def _run_external_cmd(cmd, verbose=True, env=None): if verbose: print("Running external command line application. This may print" " messages to stdout and/or stderr.\nThe command being run is" " below. This command cannot be manually re-run as it will" " depend on temporary files that no longer exist.\n\nCommand:\n") print(" ".join(cmd), end='\n\n') return subprocess.run(cmd, check=True, env=env) def _omp_cmd_wrapper(threads, cmd, verbose=True): env = environ.copy() env.update({'OMP_NUM_THREADS': str(threads)}) return _run_external_cmd(cmd, verbose=verbose, env=env) q2-diversity-lib-2024.5.0/q2_diversity_lib/_version.py000066400000000000000000000441351462552567500225520ustar00rootroot00000000000000 # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.18 (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (tag: 2024.5.0, Release-2024.5)" git_full = "061b6e199fd2464750c8e59fca899fe3353ddb4e" git_date = "2024-05-29 04:14:53 +0000" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "" cfg.parentdir_prefix = "q2-diversity-" cfg.versionfile_source = "q2_diversity/_version.py" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, p.returncode return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} q2-diversity-lib-2024.5.0/q2_diversity_lib/alpha.py000066400000000000000000000130131462552567500220020ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- import pandas as pd import numpy as np import functools import skbio.diversity import skbio.diversity.alpha import biom from q2_types.feature_table import BIOMV210Format from q2_types.sample_data import AlphaDiversityFormat from q2_types.tree import NewickFormat from ._util import (_validate_tables, _validate_requested_cpus, _omp_cmd_wrapper) from q2_diversity_lib.skbio._methods import (_berger_parker, _brillouin_d, _simpsons_dominance, _esty_ci, _goods_coverage, _margalef, _mcintosh_d, _strong, _shannon, _p_evenness) METRICS = { 'PHYLO': { 'IMPL': {'faith_pd'}, 'UNIMPL': set() }, 'NONPHYLO': { 'IMPL': {'observed_features', 'pielou_e', 'shannon'}, 'UNIMPL': {'ace', 'chao1', 'chao1_ci', 'berger_parker_d', 'brillouin_d', 'dominance', 'doubles', 'enspie', 'esty_ci', 'fisher_alpha', 'goods_coverage', 'heip_e', 'kempton_taylor_q', 'margalef', 'mcintosh_d', 'mcintosh_e', 'menhinick', 'michaelis_menten_fit', 'osd', 'robbins', 'simpson', 'simpson_e', 'singles', 'strong', 'gini_index', 'lladser_pe' } }, 'NAME_TRANSLATIONS': {'faith_pd': 'faith_pd', 'shannon': 'shannon_entropy', 'pielou_e': 'pielou_evenness', 'observed_features': 'observed_features' } } # --------------------- Phylogenetic ----------------------------------------- @_validate_tables @_validate_requested_cpus def faith_pd(table: BIOMV210Format, phylogeny: NewickFormat, threads: int = 1) -> AlphaDiversityFormat: vec = AlphaDiversityFormat() cmd = ['faithpd', '-i', str(table), '-t', str(phylogeny), '-o', str(vec)] _omp_cmd_wrapper(threads, cmd) return vec # --------------------- Non-Phylogenetic ------------------------------------- def _skbio_alpha_diversity_from_1d(v, metric): # alpha_diversity expects a 2d structure v = np.reshape(v, (1, len(v))) result = skbio.diversity.alpha_diversity(metric=metric, counts=v, ids=['placeholder', ], validate=False) return result.iloc[0] @_validate_tables def observed_features(table: biom.Table) -> pd.Series: presence_absence_table = table.pa(inplace=False) results = [] for v in presence_absence_table.iter_data(dense=True): results.append(_skbio_alpha_diversity_from_1d(v.astype(int), 'observed_otus')) results = pd.Series(results, index=table.ids(), name='observed_features') return results @_validate_tables def pielou_evenness(table: biom.Table, drop_undefined_samples: bool = False) -> pd.Series: if drop_undefined_samples: def transform_(v, i, m): if (v > 0).sum() < 2: return np.zeros(len(v)) else: return v table = table.transform(transform_, inplace=False).remove_empty() results = [] for v in table.iter_data(dense=True): # using in-house metrics temporarily # results.append(_skbio_alpha_diversity_from_1d(v, 'pielou_e')) v = np.reshape(v, (1, len(v))) results.extend([_p_evenness(c)for c in v]) results = pd.Series(results, index=table.ids(), name='pielou_evenness') return results @_validate_tables def shannon_entropy(table: biom.Table, drop_undefined_samples: bool = False) -> pd.Series: if drop_undefined_samples: table = table.remove_empty(inplace=False) results = [] for v in table.iter_data(dense=True): # using in-house metrics temporarily # results.append(_skbio_alpha_diversity_from_1d(v, 'shannon')) v = np.reshape(v, (1, len(v))) results.extend([_shannon(c)for c in v]) results = pd.Series(results, index=table.ids(), name='shannon_entropy') return results @_validate_tables def alpha_passthrough(table: biom.Table, metric: str) -> pd.Series: results = [] method_map = {"berger_parker_d": _berger_parker, "brillouin_d": _brillouin_d, "simpson": _simpsons_dominance, "esty_ci": _esty_ci, "goods_coverage": _goods_coverage, "margalef": _margalef, "mcintosh_d": _mcintosh_d, "strong": _strong} if metric in method_map: metric = functools.partial(method_map[metric]) for v in table.iter_data(dense=True): v = np.reshape(v, (1, len(v))) results.extend([metric(c)for c in v]) else: for v in table.iter_data(dense=True): results.append(_skbio_alpha_diversity_from_1d(v.astype(int), metric)) results = pd.Series(results, index=table.ids(), name=metric) return results q2-diversity-lib-2024.5.0/q2_diversity_lib/beta.py000066400000000000000000000202011462552567500216250ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- import biom import skbio.diversity import sklearn.metrics import unifrac from skbio.stats.composition import clr from scipy.spatial.distance import euclidean from scipy.spatial.distance import jensenshannon import numpy as np from q2_types.distance_matrix import LSMatFormat from q2_types.feature_table import BIOMV210Format from q2_types.tree import NewickFormat from ._util import (_validate_tables, _validate_requested_cpus, _omp_cmd_wrapper) # NOTE: some phylo metrics are currently in both implemented and unimplemented # collections, when implemented with certain params only (e.g. both 'vanilla' # and Variance Adjusted weighted unifracs use unifrac.weighted_unnormalized, # but only 'vanilla' is currently implemented) METRICS = { 'PHYLO': { 'IMPL': {'unweighted_unifrac', 'weighted_unifrac'}, 'UNIMPL': {'unweighted_unifrac', 'weighted_unifrac', 'weighted_normalized_unifrac', 'generalized_unifrac'}, }, 'NONPHYLO': { 'IMPL': {'braycurtis', 'jaccard'}, 'UNIMPL': {'cityblock', 'euclidean', 'seuclidean', 'sqeuclidean', 'cosine', 'correlation', 'hamming', 'chebyshev', 'canberra', 'yule', 'matching', 'dice', 'rogerstanimoto', 'russellrao', 'sokalmichener', 'sokalsneath', 'minkowski', 'aitchison', 'canberra_adkins', 'jensenshannon'} }, 'NAME_TRANSLATIONS': {'braycurtis': 'bray_curtis', 'jaccard': 'jaccard', 'unweighted_unifrac': 'unweighted_unifrac', 'weighted_unifrac': 'weighted_unifrac', } } # -------------------- Method Dispatch ----------------------- @_validate_tables @_validate_requested_cpus def beta_passthrough(table: biom.Table, metric: str, pseudocount: int = 1, n_jobs: int = 1) -> skbio.DistanceMatrix: def aitchison(x, y, **kwds): return euclidean(clr(x), clr(y)) def canberra_adkins(x, y, **kwds): nz = ((x > 0) | (y > 0)) x_ = x[nz] y_ = y[nz] nnz = nz.sum() return (1. / nnz) * np.sum(np.abs(x_ - y_) / (x_ + y_)) def jensen_shannon(x, y, **kwds): return jensenshannon(x, y) counts = table.matrix_data.toarray().T sample_ids = table.ids(axis='sample') if metric == 'aitchison': counts += pseudocount metric = aitchison elif metric == 'canberra_adkins': metric = canberra_adkins elif metric == 'jensenshannon': metric = jensen_shannon else: pass return skbio.diversity.beta_diversity( metric=metric, counts=counts, ids=sample_ids, validate=False, pairwise_func=sklearn.metrics.pairwise_distances, n_jobs=n_jobs) @_validate_tables @_validate_requested_cpus def beta_phylogenetic_passthrough(table: BIOMV210Format, phylogeny: NewickFormat, metric: str, threads: int = 1, variance_adjusted: bool = False, alpha: float = None, bypass_tips: bool = False ) -> LSMatFormat: # Ideally we remove this when we can support optional type-mapped params. if alpha is not None and metric != 'generalized_unifrac': raise ValueError("The alpha parameter is only allowed when the " "selected metric is 'generalized_unifrac'") method = { 'unweighted_unifrac': 'unweighted', 'weighted_unifrac': 'weighted_unnormalized', 'weighted_normalized_unifrac': 'weighted_normalized', 'generalized_unifrac': 'generalized', }[metric] result = LSMatFormat() cmd = [ 'ssu', '-i', str(table), '-t', str(phylogeny), '-m', method, '-o', str(result), ] # handle unimplemented unifracs if metric == 'generalized_unifrac': alpha = 1.0 if alpha is None else alpha cmd += ['-a', str(alpha)] if variance_adjusted: cmd += ['--vaw'] if bypass_tips: cmd += ['-f'] _omp_cmd_wrapper(threads, cmd) return result @_validate_tables @_validate_requested_cpus def beta_phylogenetic_meta_passthrough(tables: BIOMV210Format, phylogenies: NewickFormat, metric: str, threads: int = 1, variance_adjusted: bool = False, alpha: float = None, bypass_tips: bool = False, weights: list = None, consolidation: str='skipping_missing_values' # noqa ) -> skbio.DistanceMatrix: # Ideally we remove this when we can support optional type-mapped params. if alpha is not None and metric != 'generalized_unifrac': raise ValueError("The alpha parameter is only allowed when the " "selected metric is 'generalized_unifrac'") metric_map = {'unweighted_unifrac': 'unweighted', 'weighted_normalized_unifrac': 'weighted_normalized', 'weighted_unifrac': 'weighted_unnormalized', 'generalized_unifrac': 'generalized'} metric = metric_map[metric] return unifrac.meta(tuple([str(t) for t in tables]), tuple([str(p) for p in phylogenies]), weights=weights, threads=threads, consolidation=consolidation, method=metric, variance_adjusted=variance_adjusted, alpha=alpha, bypass_tips=bypass_tips) # --------------------Non-Phylogenetic----------------------- @_validate_tables @_validate_requested_cpus def bray_curtis(table: biom.Table, n_jobs: int = 1) -> skbio.DistanceMatrix: counts = table.matrix_data.toarray().T sample_ids = table.ids(axis='sample') return skbio.diversity.beta_diversity( metric='braycurtis', counts=counts, ids=sample_ids, validate=False, pairwise_func=sklearn.metrics.pairwise_distances, n_jobs=n_jobs ) @_validate_tables @_validate_requested_cpus def jaccard(table: biom.Table, n_jobs: int = 1) -> skbio.DistanceMatrix: counts = table.matrix_data.toarray().T sample_ids = table.ids(axis='sample') return skbio.diversity.beta_diversity( metric='jaccard', counts=counts, ids=sample_ids, validate=False, pairwise_func=sklearn.metrics.pairwise_distances, n_jobs=n_jobs ) # ------------------------Phylogenetic----------------------- @_validate_tables @_validate_requested_cpus def unweighted_unifrac(table: BIOMV210Format, phylogeny: NewickFormat, threads: int = 1, bypass_tips: bool = False) -> LSMatFormat: result = LSMatFormat() cmd = [ 'ssu', '-i', str(table), '-t', str(phylogeny), '-m', 'unweighted', '-o', str(result), ] if bypass_tips: cmd += ['-f'] _omp_cmd_wrapper(threads, cmd) return result @_validate_tables @_validate_requested_cpus def weighted_unifrac(table: BIOMV210Format, phylogeny: NewickFormat, threads: int = 1, bypass_tips: bool = False ) -> LSMatFormat: result = LSMatFormat() cmd = [ 'ssu', '-i', str(table), '-t', str(phylogeny), '-m', 'weighted_unnormalized', '-o', str(result), ] if bypass_tips: cmd += ['-f'] _omp_cmd_wrapper(threads, cmd) return result q2-diversity-lib-2024.5.0/q2_diversity_lib/citations.bib000066400000000000000000000412621462552567500230250ustar00rootroot00000000000000@article{lozupone2008metaunifrac, author = {Lozupone, Catherine A. and Hamady, Micah and Cantarel, Brandi L. and Coutinho, Pedro M. and Henrissat, Bernard and Gordon, Jeffrey I. and Knight, Rob}, title = {The convergence of carbohydrate active gene repertoires in human gut microbes}, volume = {105}, number = {39}, pages = {15076--15081}, year = {2008}, doi = {10.1073/pnas.0807339105}, publisher = {National Academy of Sciences}, abstract = {The extreme variation in gene content among phylogenetically related microorganisms suggests that gene acquisition, expansion, and loss are important evolutionary forces for adaptation to new environments. Accordingly, phylogenetically disparate organisms that share a habitat may converge in gene content as they adapt to confront shared challenges. This response should be especially pronounced for functional genes that are important for survival in a particular habitat. We illustrate this principle by showing that the repertoires of two different types of carbohydrate-active enzymes, glycoside hydrolases and glycosyltransferases, have converged in bacteria and archaea that live in the human gut and that this convergence is largely due to horizontal gene transfer rather than gene family expansion. We also identify gut microbes that may have more similar dietary niches in the human gut than would be expected based on phylogeny. The techniques used to obtain these results should be broadly applicable to understanding the functional genes and evolutionary processes important for adaptation in many environments and useful for interpreting the large number of reference microbial genome sequences being generated for the International Human Microbiome Project.}, issn = {0027-8424}, URL = {https://www.pnas.org/content/105/39/15076}, eprint = {https://www.pnas.org/content/105/39/15076.full.pdf}, journal = {Proceedings of the National Academy of Sciences} } @article{faith1992conservation, title = {Conservation evaluation and phylogenetic diversity}, author = {Faith, Daniel P}, journal = {Biological conservation}, volume = {61}, number = {1}, pages = {1--10}, year = {1992}, publisher = {Elsevier}, doi = {10.1016/0006-3207(92)91201-3} } @article{pielou1966measurement, title = {The measurement of diversity in different types of biological collections}, author = {Pielou, Evelyn C}, journal = {Journal of theoretical biology}, volume = {13}, pages = {131--144}, year = {1966}, publisher = {Elsevier}, doi = {10.1016/0022-5193(66)90013-0} } % TODO: Selected during a preliminary assay of the literature - must be confirmed/augmented @article{shannon1948communication, title = {A mathematical theory of communication}, author = {Shannon, Claude E}, journal = {The Bell System Technical Journal}, volume = {27}, issue = {3}, pages = {379--423, 623--656}, year = {1948}, publisher = {Nokia Bell Labs}, doi = {10.1002/j.1538-7305.1948.tb01338.x} } % TODO: Selected during a preliminary assay of the literature - must be confirmed/augmented @article{jaccard1908nouvelles, title = {Nouvelles recherches sur la distribution floral}, author = {Jaccard, P}, journal = {Bull. Soc. Vard. Sci. Nat}, volume = {44}, pages = {223--270}, year = {1908} } % TODO: Selected during a preliminary assay of the literature - must be confirmed/augmented @article{sorensen1948method, title = {A method of establishing groups of equal amplitude in plant sociology based on similarity of species and its application to analyses of the vegetation on Danish commons}, author = {S{\o}rensen, Thorvald}, journal = {Biol. Skr.}, volume = {5}, pages = {1--34}, year = {1948} } @article{mcdonald2018unifrac, title = {Striped UniFrac: enabling microbiome analysis at unprecedented scale}, author = {McDonald, Daniel and Vázquez-Baeza, Yoshiki and Koslicki, David and McClelland, Jason and Reeve, Nicolai and Xu, Zhenjiang and Gonzalez, Antonio and Knight, Rob}, journal = {Nature Methods}, volume = {15}, issue = {11}, pages = {847--848}, issn = {1548-7105}, year = {2018}, doi = {10.1038/s41592-018-0187-8} } @article{sfiligoi2022unifrac, title = {Optimizing UniFrac with OpenACC yields >1000x speed increase}, author = {Sfiligoi, Igor and Armstrong, George and Gonzalez, Antonio and McDonald, Daniel and Knight, Rob}, journal = {mSystems}, year = {in press} } @article{armstrong2021faithspd, author = {Armstrong, George and Cantrell, Kalen and Huang, Shi and McDonald, Daniel and Haiminen, Niina and Carrieri, Anna Paola and Zhu, Qiyun and Gonzalez, Antonio and McGrath, Imran and Beck, Kristen L. and Hakim, Daniel and Havulinna, Aki S. and Méric, Guillaume and Niiranen, Teemu and Lahti, Leo and Salomaa, Veikko and Jain, Mohit and Inouye, Michael and Swafford, Austin D. and Kim, Ho-Cheol and Parida, Laxmi and Vázquez-Baeza, Yoshiki and Knight, Rob}, title = {Efficient computation of Faith's phylogenetic diversity with applications in characterizing microbiomes}, volume = {31}, number = {11}, pages = {2131-2137}, year = {2021}, doi = {10.1101/gr.275777.121}, abstract ={The number of publicly available microbiome samples is continually growing. As data set size increases, bottlenecks arise in standard analytical pipelines. Faith's phylogenetic diversity (Faith's PD) is a highly utilized phylogenetic alpha diversity metric that has thus far failed to effectively scale to trees with millions of vertices. Stacked Faith's phylogenetic diversity (SFPhD) enables calculation of this widely adopted diversity metric at a much larger scale by implementing a computationally efficient algorithm. The algorithm reduces the amount of computational resources required, resulting in more accessible software with a reduced carbon footprint, as compared to previous approaches. The new algorithm produces identical results to the previous method. We further demonstrate that the phylogenetic aspect of Faith's PD provides increased power in detecting diversity differences between younger and older populations in the FINRISK study's metagenomic data.}, URL = {http://genome.cshlp.org/content/31/11/2131.abstract}, eprint = {http://genome.cshlp.org/content/31/11/2131.full.pdf+html}, journal = {Genome Research} } @article {lozupone2005unifrac, author = {Lozupone, Catherine and Knight, Rob}, title = {UniFrac: a New Phylogenetic Method for Comparing Microbial Communities}, volume = {71}, number = {12}, pages = {8228--8235}, year = {2005}, doi = {10.1128/AEM.71.12.8228-8235.2005}, publisher = {American Society for Microbiology Journals}, abstract = {We introduce here a new method for computing differences between microbial communities based on phylogenetic information. This method, UniFrac, measures the phylogenetic distance between sets of taxa in a phylogenetic tree as the fraction of the branch length of the tree that leads to descendants from either one environment or the other, but not both. UniFrac can be used to determine whether communities are significantly different, to compare many communities simultaneously using clustering and ordination techniques, and to measure the relative contributions of different factors, such as chemistry and geography, to similarities between samples. We demonstrate the utility of UniFrac by applying it to published 16S rRNA gene libraries from cultured isolates and environmental clones of bacteria in marine sediment, water, and ice. Our results reveal that (i) cultured isolates from ice, water, and sediment resemble each other and environmental clone sequences from sea ice, but not environmental clone sequences from sediment and water; (ii) the geographical location does not correlate strongly with bacterial community differences in ice and sediment from the Arctic and Antarctic; and (iii) bacterial communities differ between terrestrially impacted seawater (whether polar or temperate) and warm oligotrophic seawater, whereas those in individual seawater samples are not more similar to each other than to those in sediment or ice samples. These results illustrate that UniFrac provides a new way of characterizing microbial communities, using the wealth of environmental rRNA sequences, and allows quantitative insight into the factors that underlie the distribution of lineages among environments.}, issn = {0099-2240}, URL = {https://aem.asm.org/content/71/12/8228}, eprint = {https://aem.asm.org/content/71/12/8228.full.pdf}, journal = {Applied and Environmental Microbiology} } @article {lozupone2007unifrac, author = {Lozupone, Catherine A. and Hamady, Micah and Kelley, Scott T. and Knight, Rob}, title = {Quantitative and Qualitative β Diversity Measures Lead to Different Insights into Factors That Structure Microbial Communities}, volume = {73}, number = {5}, pages = {1576--1585}, year = {2007}, doi = {10.1128/AEM.01996-06}, publisher = {American Society for Microbiology Journals}, abstract = {The assessment of microbial diversity and distribution is a major concern in environmental microbiology. There are two general approaches for measuring community diversity: quantitative measures, which use the abundance of each taxon, and qualitative measures, which use only the presence/absence of data. Quantitative measures are ideally suited to revealing community differences that are due to changes in relative taxon abundance (e.g., when a particular set of taxa flourish because a limiting nutrient source becomes abundant). Qualitative measures are most informative when communities differ primarily by what can live in them (e.g., at high temperatures), in part because abundance information can obscure significant patterns of variation in which taxa are present. We illustrate these principles using two 16S rRNA-based surveys of microbial populations and two phylogenetic measures of community β diversity: unweighted UniFrac, a qualitative measure, and weighted UniFrac, a new quantitative measure, which we have added to the UniFrac website (http://bmf.colorado.edu/unifrac). These studies considered the relative influences of mineral chemistry, temperature, and geography on microbial community composition in acidic thermal springs in Yellowstone National Park and the influences of obesity and kinship on microbial community composition in the mouse gut. We show that applying qualitative and quantitative measures to the same data set can lead to dramatically different conclusions about the main factors that structure microbial diversity and can provide insight into the nature of community differences. We also demonstrate that both weighted and unweighted UniFrac measurements are robust to the methods used to build the underlying phylogeny.}, issn = {0099-2240}, URL = {https://aem.asm.org/content/73/5/1576}, eprint = {https://aem.asm.org/content/73/5/1576.full.pdf}, journal = {Applied and Environmental Microbiology} } @article {hamady2010unifrac, author = {Hamady, Micah and Lozupone, Catherine and Knight, Rob}, title = {Fast UniFrac: facilitating high-throughput phylogenetic analyses of microbial communities including analysis of pyrosequencing and PhyloChip data}, journal = {The ISME Journal}, pages = {17--27}, volume = {4}, issue = {1}, abstract = {Next-generation sequencing techniques, and PhyloChip, have made simultaneous phylogenetic analyses of hundreds of microbial communities possible. Insight into community structure has been limited by the inability to integrate and visualize such vast datasets. Fast UniFrac overcomes these issues, allowing integration of larger numbers of sequences and samples into a single analysis. Its new array-based implementation offers orders of magnitude improvements over the original version. New 3D visualization of principal coordinates analysis results, with the option to view multiple coordinate axes simultaneously, provides a powerful way to quickly identify patterns that relate vast numbers of microbial communities. We show the potential of Fast UniFrac using examples from three data types: Sanger-sequencing studies of diverse free-living and animal-associated bacterial assemblages and from the gut of obese humans as they diet, pyrosequencing data integrated from studies of the human hand and gut, and PhyloChip data from a study of citrus pathogens. We show that a Fast UniFrac analysis using a reference tree recaptures patterns that could not be detected without considering phylogenetic relationships and that Fast UniFrac, coupled with BLAST-based sequence assignment, can be used to quickly analyze pyrosequencing runs containing hundreds of thousands of sequences, showing patterns relating human and gut samples. Finally, we show that the application of Fast UniFrac to PhyloChip data could identify well-defined subcategories associated with infection. Together, these case studies point the way toward a broad range of applications and show some of the new features of Fast UniFrac.}, issn = {1751-7370}, doi = {10.1038/ismej.2009.97}, year = {2010} } @article {lozupone2011unifrac, author = {Lozupone, Catherine and Lladser, Manuel E and Knights, Dan and Stombaugh, Jesse and Knight, Rob}, title = {UniFrac: an effective distance metric for microbial community comparison}, journal = {The ISME Journal}, pages = {169--172}, volume = {5}, issue = {2}, issn = {1751-7370}, doi = {10.1038/ismej.2010.133}, year = {2011} } @article {chen2012genUnifrac, author = {Chen, Jun and Bittinger, Kyle and Charlson, Emily S. and Hoffmann, Christian and Lewis, James and Wu, Gary D. and Collman, Ronald G. and Bushman, Frederic D. and Li, Hongzhe}, title = {Associating microbiome composition with environmental covariates using generalized UniFrac distances}, journal = {Bioinformatics}, volume = {28}, number = {16}, pages = {2106--2113}, year = {2012}, month = {06}, abstract = "{Motivation: The human microbiome plays an important role in human disease and health. Identification of factors that affect the microbiome composition can provide insights into disease mechanism as well as suggest ways to modulate the microbiome composition for therapeutical purposes. Distance-based statistical tests have been applied to test the association of microbiome composition with environmental or biological covariates. The unweighted and weighted UniFrac distances are the most widely used distance measures. However, these two measures assign too much weight either to rare lineages or to most abundant lineages, which can lead to loss of power when the important composition change occurs in moderately abundant lineages.Results: We develop generalized UniFrac distances that extend the weighted and unweighted UniFrac distances for detecting a much wider range of biologically relevant changes. We evaluate the use of generalized UniFrac distances in associating microbiome composition with environmental covariates using extensive Monte Carlo simulations. Our results show that tests using the unweighted and weighted UniFrac distances are less powerful in detecting abundance change in moderately abundant lineages. In contrast, the generalized UniFrac distance is most powerful in detecting such changes, yet it retains nearly all its power for detecting rare and highly abundant lineages. The generalized UniFrac distance also has an overall better power than the joint use of unweighted/weighted UniFrac distances. Application to two real microbiome datasets has demonstrated gains in power in testing the associations between human microbiome and diet intakes and habitual smoking.Availability:http://cran.r-project.org/web/packages/GUniFracContact:hongzhe@upenn.eduSupplementary information:Supplementary data are available at Bioinformatics online.}", issn = {1367-4803}, doi = {10.1093/bioinformatics/bts342}, url = {https://doi.org/10.1093/bioinformatics/bts342}, eprint = {http://oup.prod.sis.lan/bioinformatics/article-pdf/28/16/2106/16905186/bts342.pdf} } @article{chang2011variance, author = {Chang, Qin and Luan, Yihui and Sun, Fengzhu}, title = {Variance adjusted weighted UniFrac: a powerful beta diversity measure for comparing communities based on phylogeny}, journal = {BMC Bioinformatics}, year = {2011}, month = {Apr}, ay = {25}, volume = {12}, number = {1}, pages = {118}, abstract = {Beta diversity, which involves the assessment of differences between communities, is an important problem in ecological studies. Many statistical methods have been developed to quantify beta diversity, and among them, UniFrac and weighted-UniFrac (W-UniFrac) are widely used. The W-UniFrac is a weighted sum of branch lengths in a phylogenetic tree of the sequences from the communities. However, W-UniFrac does not consider the variation of the weights under random sampling resulting in less power detecting the differences between communities.}, issn = {1471-2105}, doi = {10.1186/1471-2105-12-118}, url = {https://doi.org/10.1186/1471-2105-12-118} } q2-diversity-lib-2024.5.0/q2_diversity_lib/examples.py000066400000000000000000000474361462552567500225530ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- import biom import numpy as np import pkg_resources from qiime2 import Artifact def get_test_data_path(filename): return pkg_resources.resource_filename('q2_diversity_lib.tests', f'data/{filename}') s_ids_1 = ['S1', 'S2', 'S3', 'S4', 'S5'] s_ids_2 = ['S6', 'S7', 'S8', 'S9', 'S10'] def ft1_factory(): return Artifact.import_data( 'FeatureTable[Frequency]', biom.Table(np.array([[1, 0, 5, 999, 1], [0, 1, 2, 0, 5], [0, 0, 0, 1, 10]]), ['A', 'B', 'C'], s_ids_1) ) def ft2_factory(): return Artifact.import_data( 'FeatureTable[Frequency]', biom.Table(np.array([[1, 10, 5, 999, 1], [5, 1, 2, 0, 5], [1, 40, 30, 10, 0]]), ['A', 'B', 'C'], s_ids_2) ) def tree_factory(): input_tree_fp = get_test_data_path('faith_test.tree') return Artifact.import_data( 'Phylogeny[Rooted]', input_tree_fp ) # ------------------------ alpha-diversity ----------------------- def faith_pd_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='faith_pd'), use.UsageInputs(table=ft, phylogeny=tree), use.UsageOutputNames(vector='faith_pd_vector')) result.assert_output_type('SampleData[AlphaDiversity]') def observed_features_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='observed_features'), use.UsageInputs(table=ft), use.UsageOutputNames(vector='obs_feat_vector')) result.assert_output_type('SampleData[AlphaDiversity]') # AssertRegex for lines in output files exp = zip(s_ids_1, [1, 1, 2, 2, 3]) for id, val in exp: result.assert_has_line_matching( path='alpha-diversity.tsv', expression=f'{id}\t{val}' ) def pielou_evenness_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='pielou_evenness'), use.UsageInputs(table=ft), use.UsageOutputNames(vector='pielou_vector') ) result.assert_output_type('SampleData[AlphaDiversity]') def pielou_drop_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='pielou_evenness'), use.UsageInputs(table=ft, drop_undefined_samples=True), use.UsageOutputNames(vector='pielou_vector') ) result.assert_output_type('SampleData[AlphaDiversity]') def shannon_entropy_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='shannon_entropy'), use.UsageInputs(table=ft), use.UsageOutputNames(vector='shannon_vector') ) result.assert_output_type('SampleData[AlphaDiversity]') def shannon_drop_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='shannon_entropy'), use.UsageInputs(table=ft, drop_undefined_samples=True), use.UsageOutputNames(vector='shannon_vector') ) result.assert_output_type('SampleData[AlphaDiversity]') # ------------------------ beta-diversity ----------------------- def bray_curtis_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='bray_curtis'), use.UsageInputs(table=ft), use.UsageOutputNames(distance_matrix='bray_curtis_dm') ) result.assert_output_type('DistanceMatrix') def bray_curtis_n_jobs_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='bray_curtis'), use.UsageInputs(table=ft, n_jobs=1), use.UsageOutputNames(distance_matrix='bray_curtis_dm') ) result.assert_output_type('DistanceMatrix') def bray_curtis_auto_jobs_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='bray_curtis'), use.UsageInputs(table=ft, n_jobs='auto'), use.UsageOutputNames(distance_matrix='bray_curtis_dm') ) result.assert_output_type('DistanceMatrix') def jaccard_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='jaccard'), use.UsageInputs(table=ft), use.UsageOutputNames(distance_matrix='jaccard_dm') ) result.assert_output_type('DistanceMatrix') def jaccard_n_jobs_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='jaccard'), use.UsageInputs(table=ft, n_jobs=1), use.UsageOutputNames(distance_matrix='jaccard_dm') ) result.assert_output_type('DistanceMatrix') def jaccard_auto_jobs_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='jaccard'), use.UsageInputs(table=ft, n_jobs='auto'), use.UsageOutputNames(distance_matrix='jaccard_dm') ) result.assert_output_type('DistanceMatrix') def u_u_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='unweighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree), use.UsageOutputNames(distance_matrix='unweighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def u_u_n_threads_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='unweighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree, threads=1), use.UsageOutputNames(distance_matrix='unweighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def u_u_auto_threads_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='unweighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree, threads='auto'), use.UsageOutputNames(distance_matrix='unweighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def u_u_bypass_tips_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) use.comment("bypass_tips can be used with any threads setting, " "but auto may be a good choice if you're trimming run time.") result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='unweighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree, threads='auto', bypass_tips=True), use.UsageOutputNames(distance_matrix='unweighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def w_u_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='weighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree), use.UsageOutputNames(distance_matrix='weighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def w_u_n_threads_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='weighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree, threads=1), use.UsageOutputNames(distance_matrix='weighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def w_u_auto_threads_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='weighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree, threads='auto'), use.UsageOutputNames(distance_matrix='weighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def w_u_bypass_tips_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) use.comment("bypass_tips can be used with any threads setting, " "but auto may be a good choice if you're trimming run time.") result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='weighted_unifrac'), use.UsageInputs(table=ft, phylogeny=tree, threads='auto', bypass_tips=True), use.UsageOutputNames(distance_matrix='weighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') # ------------------------ Passthrough Methods ------------------------ def alpha_passthrough_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='alpha_passthrough'), use.UsageInputs(table=ft, metric='simpson'), use.UsageOutputNames(vector='simpson_vector') ) result.assert_output_type('SampleData[AlphaDiversity]') def beta_passthrough_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_passthrough'), use.UsageInputs(table=ft, metric='euclidean'), use.UsageOutputNames(distance_matrix='euclidean_dm') ) result.assert_output_type('DistanceMatrix') def beta_passthrough_n_jobs_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_passthrough'), use.UsageInputs(table=ft, metric='euclidean', n_jobs=1), use.UsageOutputNames(distance_matrix='euclidean_dm') ) result.assert_output_type('DistanceMatrix') def beta_passthrough_auto_jobs_example(use): ft = use.init_artifact('feature_table', ft1_factory) use.comment("A default pseudocount of 1 is added to feature counts. " "Pseudocount is ignored for non-compositional metrics.") result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_passthrough'), use.UsageInputs(table=ft, metric='aitchison', n_jobs='auto'), use.UsageOutputNames(distance_matrix='aitchison_dm') ) result.assert_output_type('DistanceMatrix') def beta_passthrough_pseudocount_example(use): ft = use.init_artifact('feature_table', ft1_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_passthrough'), use.UsageInputs(table=ft, metric='aitchison', n_jobs='auto', pseudocount=5), use.UsageOutputNames(distance_matrix='aitchison_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_passthrough_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_passthrough'), use.UsageInputs(table=ft, phylogeny=tree, metric='weighted_normalized_unifrac'), use.UsageOutputNames(distance_matrix='weighted_normalized_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_passthrough_n_threads_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_passthrough'), use.UsageInputs(table=ft, phylogeny=tree, threads=1, metric='weighted_normalized_unifrac'), use.UsageOutputNames(distance_matrix='weighted_normalized_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_passthrough_auto_threads_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_passthrough'), use.UsageInputs(table=ft, phylogeny=tree, threads='auto', metric='weighted_normalized_unifrac'), use.UsageOutputNames(distance_matrix='weighted_normalized_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_passthrough_bypass_tips_example(use): ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) use.comment("bypass_tips can be used with any threads setting, " "but auto may be a good choice if you're trimming run time.") result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_passthrough'), use.UsageInputs(table=ft, phylogeny=tree, metric='weighted_normalized_unifrac', threads='auto', bypass_tips=True), use.UsageOutputNames(distance_matrix='weighted_normalized_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_passthrough_variance_adjusted_example(use): use.comment( "Chang et al's variance adjustment may be applied to any unifrac " "method by using this passthrough function.") ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_passthrough'), use.UsageInputs(table=ft, phylogeny=tree, metric='weighted_unifrac', threads='auto', variance_adjusted=True), use.UsageOutputNames(distance_matrix='var_adj_weighted_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_passthrough_min_generalized_unifrac_example(use): use.comment( "Generalized unifrac is passed alpha=1 by default. " "This is roughly equivalent to weighted normalized unifrac, " "which method will be used instead, because it is better optimized." ) ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_passthrough'), use.UsageInputs(table=ft, phylogeny=tree, metric='generalized_unifrac'), use.UsageOutputNames(distance_matrix='generalized_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_passthrough_generalized_unifrac_example(use): use.comment("passing a float between 0 and 1 to 'alpha' gives you control " "over the importance of sample proportions.") ft = use.init_artifact('feature_table', ft1_factory) tree = use.init_artifact('phylogeny', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_passthrough'), use.UsageInputs(table=ft, phylogeny=tree, metric='generalized_unifrac', alpha=0.75), use.UsageOutputNames(distance_matrix='generalized_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_meta_passthrough_example(use): use.comment( "For brevity, these examples are focused on meta-specific parameters. " "See the documentation for beta_phylogenetic_passthrough for " "additional relevant information." ) ft1 = use.init_artifact('feature_table1', ft1_factory) ft2 = use.init_artifact('feature_table2', ft2_factory) tree1 = use.init_artifact('phylogeny1', tree_factory) tree2 = use.init_artifact('phylogeny2', tree_factory) use.comment("NOTE: the number of trees and tables must match.") result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_meta_passthrough'), use.UsageInputs(tables=[ft1, ft2], phylogenies=[tree1, tree2], metric='weighted_normalized_unifrac'), use.UsageOutputNames(distance_matrix='ft1_ft2_w_norm_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_meta_weights_example(use): ft1 = use.init_artifact('feature_table1', ft1_factory) ft2 = use.init_artifact('feature_table2', ft2_factory) tree = use.init_artifact('phylogeny', tree_factory) use.comment("The number of weights must match the number of tables/trees.") use.comment("If meaningful, it is possible to pass the same phylogeny " "more than once.") result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_meta_passthrough'), use.UsageInputs(tables=[ft1, ft2], phylogenies=[tree, tree], metric='weighted_normalized_unifrac', weights=[3.0, 42.0]), use.UsageOutputNames(distance_matrix='ft1_ft2_w_norm_unifrac_dm') ) result.assert_output_type('DistanceMatrix') def beta_phylo_meta_consolidation_example(use): ft1 = use.init_artifact('feature_table1', ft1_factory) ft2 = use.init_artifact('feature_table2', ft2_factory) tree1 = use.init_artifact('phylogeny1', tree_factory) tree2 = use.init_artifact('phylogeny2', tree_factory) result, = use.action( use.UsageAction(plugin_id='diversity_lib', action_id='beta_phylogenetic_meta_passthrough'), use.UsageInputs(tables=[ft1, ft2], phylogenies=[tree1, tree2], metric='weighted_normalized_unifrac', weights=[0.4, 0.6], consolidation='skipping_missing_values'), use.UsageOutputNames(distance_matrix='ft1_ft2_w_norm_unifrac_dm') ) result.assert_output_type('DistanceMatrix') q2-diversity-lib-2024.5.0/q2_diversity_lib/plugin_setup.py000066400000000000000000000575631462552567500234550ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from qiime2.plugin import (Plugin, Citations, Bool, Int, Range, Choices, Str, Float, List, Threads) from q2_types.feature_table import (FeatureTable, Frequency, RelativeFrequency, PresenceAbsence) from q2_types.tree import Phylogeny, Rooted from q2_types.sample_data import AlphaDiversity, SampleData from q2_types.distance_matrix import DistanceMatrix from unifrac._meta import CONSOLIDATIONS from . import alpha, beta, __version__, examples citations = Citations.load('citations.bib', package='q2_diversity_lib') plugin = Plugin( name="diversity-lib", version=__version__, website="https://github.com/qiime2/q2-diversity-lib", short_description="Plugin for computing community diversity.", package="q2_diversity_lib", description="This QIIME 2 plugin computes individual metrics for " " community alpha and beta diversity.", ) n_jobs_description = ( "The number of concurrent jobs to use in performing this calculation. " "May not exceed the number of available physical cores. If n_jobs = " "'auto', one job will be launched for each identified CPU core on the " "host." ) threads_description = ( "The number of CPU threads to use in performing this calculation. " "May not exceed the number of available physical cores. If threads = " "'auto', one thread will be created for each identified CPU core on the " "host." ) drop_undef_samples_description = ( "When calculating some metrics, samples with fewer than a predetermined " "number of features produce undefined (NaN) values. If true, affected " "samples are dropped from metrics with 'drop_undefined_samples' " "implemented. For metrics without a 'drop_undefined_samples' parameter, " "this value will be ignored and no samples will be dropped." ) # ------------------------ alpha-diversity ----------------------- plugin.methods.register_function( function=alpha.faith_pd, inputs={'table': FeatureTable[Frequency | RelativeFrequency | PresenceAbsence], 'phylogeny': Phylogeny[Rooted]}, parameters={'threads': Threads}, outputs=[('vector', SampleData[AlphaDiversity])], input_descriptions={ 'table': "The feature table containing the samples for which Faith's " "phylogenetic diversity should be computed. Table values " "will be converted to presence/absence.", 'phylogeny': "Phylogenetic tree containing tip identifiers that " "correspond to the feature identifiers in the table. " "This tree can contain tip ids that are not present in " "the table, but all feature ids in the table must be " "present in this tree."}, parameter_descriptions={'threads': threads_description}, output_descriptions={'vector': "Vector containing per-sample values for " "Faith's Phylogenetic Diversity."}, name="Faith's Phylogenetic Diversity", description="Computes Faith's Phylogenetic Diversity for all samples in " "a feature table.", examples={'basic': examples.faith_pd_example}, citations=[citations['faith1992conservation'], citations['armstrong2021faithspd']] ) plugin.methods.register_function( function=alpha.observed_features, inputs={'table': FeatureTable[Frequency | RelativeFrequency | PresenceAbsence]}, parameters=None, outputs=[('vector', SampleData[AlphaDiversity])], input_descriptions={'table': "The feature table containing the samples " "for which the number of observed features should be " "calculated. Table values will be converted to " "presence/absence."}, parameter_descriptions=None, output_descriptions={'vector': "Vector containing per-sample counts of " "observed features."}, name="Observed Features", description="Compute the number of observed features for each sample in a " "feature table", examples={'basic': examples.observed_features_example} ) plugin.methods.register_function( function=alpha.pielou_evenness, inputs={'table': FeatureTable[Frequency | RelativeFrequency]}, parameters={'drop_undefined_samples': Bool}, outputs=[('vector', SampleData[AlphaDiversity])], input_descriptions={'table': "The feature table containing the samples " "for which Pielou's evenness should be computed."}, parameter_descriptions={'drop_undefined_samples': "Samples with fewer than" " two observed features produce undefined (NaN) " "values. If true, these samples are dropped " "from the output vector."}, output_descriptions={'vector': "Vector containing per-sample values " "for Pielou's Evenness."}, name="Pielou's Evenness", description="Compute Pielou's Evenness for each sample in a " "feature table", examples={'basic': examples.pielou_evenness_example, 'dropping undefined samples': examples.pielou_drop_example}, citations=[citations['pielou1966measurement']] ) # TODO: Augment citations as needed plugin.methods.register_function( function=alpha.shannon_entropy, inputs={'table': FeatureTable[Frequency | RelativeFrequency]}, parameters={'drop_undefined_samples': Bool}, outputs=[('vector', SampleData[AlphaDiversity])], input_descriptions={'table': "The feature table containing the samples " "for which Shannon's Entropy should be computed."}, parameter_descriptions={'drop_undefined_samples': "Samples with no " "observed features produce undefined (NaN) values." " If true, these samples are dropped from the " "output vector."}, output_descriptions={'vector': "Vector containing per-sample values " "for Shannon's Entropy."}, name="Shannon's Entropy", description="Compute Shannon's Entropy for each sample in a " "feature table", examples={'basic': examples.shannon_entropy_example, 'dropping undefined samples': examples.shannon_drop_example}, citations=[citations['shannon1948communication']] ) # ------------------------ beta-diversity ----------------------- # TODO: Augment citations as needed plugin.methods.register_function( function=beta.bray_curtis, inputs={'table': FeatureTable[Frequency | RelativeFrequency]}, parameters={'n_jobs': Threads}, outputs=[('distance_matrix', DistanceMatrix)], input_descriptions={ 'table': "The feature table containing the samples for which " "Bray-Curtis dissimilarity should be computed."}, parameter_descriptions={'n_jobs': n_jobs_description}, output_descriptions={ 'distance_matrix': "Distance matrix for Bray-Curtis dissimilarity"}, name="Bray-Curtis Dissimilarity", description="Compute Bray-Curtis dissimilarity for each sample in a " "feature table. Note: Frequency and relative frequency data " "produce different results unless overall sample sizes are " "identical. Please consider the impact on your results if " "you use Bray-Curtis with count data that has not been " "adjusted (normalized).", examples={"run on one core (by default)": examples.bray_curtis_example, "to run on n cores, replace 1 here with your preferred integer": examples.bray_curtis_n_jobs_example, "use 'auto' to run on all of host system's available CPU cores": examples.bray_curtis_auto_jobs_example, }, citations=[citations['sorensen1948method']]) # TODO: Augment citations as needed plugin.methods.register_function( function=beta.jaccard, inputs={'table': FeatureTable[Frequency | RelativeFrequency | PresenceAbsence]}, parameters={'n_jobs': Threads}, outputs=[('distance_matrix', DistanceMatrix)], input_descriptions={ 'table': "The feature table containing the samples for which " "Jaccard distance should be computed."}, parameter_descriptions={'n_jobs': n_jobs_description}, output_descriptions={ 'distance_matrix': "Distance matrix for Jaccard index"}, name="Jaccard Distance", description="Compute Jaccard distance for each sample " "in a feature table. Jaccard is calculated using" "presence/absence data. Data of type " "FeatureTable[Frequency | Relative Frequency] is reduced" "to presence/absence prior to calculation.", examples={"run on one core (by default)": examples.jaccard_example, "to run on n cores, replace 1 here with your preferred integer": examples.jaccard_n_jobs_example, "use 'auto' to run on all of host system's available CPU cores": examples.jaccard_auto_jobs_example, }, citations=[citations['jaccard1908nouvelles']]) plugin.methods.register_function( function=beta.unweighted_unifrac, inputs={'table': FeatureTable[Frequency | RelativeFrequency | PresenceAbsence], 'phylogeny': Phylogeny[Rooted]}, parameters={'threads': Threads, 'bypass_tips': Bool}, outputs=[('distance_matrix', DistanceMatrix)], input_descriptions={ 'table': "The feature table containing the samples for which " "Unweighted Unifrac should be computed.", 'phylogeny': "Phylogenetic tree containing tip identifiers that " "correspond to the feature identifiers in the table. " "This tree can contain tip ids that are not present in " "the table, but all feature ids in the table must be " "present in this tree."}, parameter_descriptions={ 'threads': threads_description, 'bypass_tips': ("In a bifurcating tree, the tips make up about 50% of " "the nodes in a tree. By ignoring them, specificity " "can be traded for reduced compute time. This has the" " effect of collapsing the phylogeny, and is analogous" " (in concept) to moving from 99% to 97% OTUs")}, output_descriptions={'distance_matrix': "Distance matrix for Unweighted " "Unifrac."}, name="Unweighted Unifrac", description="Compute Unweighted Unifrac for each sample in a " "feature table", examples={"run on one core (by default)": examples.u_u_example, "to run on n cores, replace 1 here with your preferred integer": examples.u_u_n_threads_example, "use 'auto' to run on all of host system's available CPU cores": examples.u_u_auto_threads_example, "use bypass_tips to trade specificity for reduced compute time": examples.u_u_bypass_tips_example, }, citations=[ citations['lozupone2005unifrac'], citations['lozupone2007unifrac'], citations['hamady2010unifrac'], citations['lozupone2011unifrac'], citations['mcdonald2018unifrac']] ) plugin.methods.register_function( function=beta.weighted_unifrac, inputs={'table': FeatureTable[Frequency | RelativeFrequency], 'phylogeny': Phylogeny[Rooted]}, parameters={'threads': Threads, 'bypass_tips': Bool}, outputs=[('distance_matrix', DistanceMatrix)], input_descriptions={ 'table': "The feature table containing the samples for which " "Weighted Unifrac should be computed.", 'phylogeny': "Phylogenetic tree containing tip identifiers that " "correspond to the feature identifiers in the table. " "This tree can contain tip ids that are not present in " "the table, but all feature ids in the table must be " "present in this tree."}, parameter_descriptions={ 'threads': threads_description, 'bypass_tips': ("In a bifurcating tree, the tips make up about 50% of " "the nodes in a tree. By ignoring them, specificity " "can be traded for reduced compute time. This has the" " effect of collapsing the phylogeny, and is analogous" " (in concept) to moving from 99% to 97% OTUs")}, output_descriptions={'distance_matrix': "Distance matrix for Unweighted " "Unifrac."}, name="Weighted Unifrac", description="Compute Weighted Unifrac for each sample in a " "feature table", examples={"run on one core (by default)": examples.w_u_example, "to run on n cores, replace 1 here with your preferred integer": examples.w_u_n_threads_example, "use 'auto' to run on all of host system's available CPU cores": examples.w_u_auto_threads_example, "use bypass_tips to trade specificity for reduced compute time": examples.w_u_bypass_tips_example, }, citations=[ citations['lozupone2005unifrac'], citations['lozupone2007unifrac'], citations['hamady2010unifrac'], citations['lozupone2011unifrac'], citations['mcdonald2018unifrac']] ) # ------------------------ Passthrough Methods ------------------------ plugin.methods.register_function( function=alpha.alpha_passthrough, inputs={'table': FeatureTable[Frequency]}, parameters={'metric': Str % Choices(alpha.METRICS['NONPHYLO']['UNIMPL'])}, outputs=[('vector', SampleData[AlphaDiversity])], input_descriptions={'table': "The feature table containing the samples " "for which a selected metric should be computed."}, parameter_descriptions={'metric': 'The alpha diversity metric to be computed.'}, output_descriptions={'vector': "Vector containing per-sample values " "for the chosen metric."}, name="Alpha Passthrough (non-phylogenetic)", description="Computes a vector of values (one value for each samples in a " "feature table) using the scikit-bio implementation of the " "selected alpha diversity metric.", examples={'basic': examples.alpha_passthrough_example}, ) plugin.methods.register_function( function=beta.beta_passthrough, inputs={'table': FeatureTable[Frequency]}, parameters={'metric': Str % Choices(beta.METRICS['NONPHYLO']['UNIMPL']), 'pseudocount': Int % Range(1, None), 'n_jobs': Threads}, outputs=[('distance_matrix', DistanceMatrix)], input_descriptions={ 'table': 'The feature table containing the samples over which beta ' 'diversity should be computed.' }, parameter_descriptions={ 'metric': 'The beta diversity metric to be computed.', 'pseudocount': 'A pseudocount to handle zeros for compositional ' 'metrics. This is ignored for non-compositional ' 'metrics.', 'n_jobs': n_jobs_description }, output_descriptions={'distance_matrix': 'The resulting distance matrix.'}, name='Beta Passthrough (non-phylogenetic)', description="Computes a distance matrix for all pairs of samples in a " "feature table using the scikit-bio implementation of the " "selected beta diversity metric.", examples={"run on one core (by default)": examples.beta_passthrough_example, "to run on n cores, replace 1 here with your preferred integer": examples.beta_passthrough_n_jobs_example, "use 'auto' to run on all of host system's available CPU cores": examples.beta_passthrough_auto_jobs_example, "use 'pseudocount' to manually set a pseudocount for " + "compositional metrics": examples.beta_passthrough_pseudocount_example, }, ) plugin.methods.register_function( function=beta.beta_phylogenetic_passthrough, inputs={'table': FeatureTable[Frequency], 'phylogeny': Phylogeny[Rooted]}, parameters={'metric': Str % Choices(beta.METRICS['PHYLO']['UNIMPL']), 'threads': Threads, 'variance_adjusted': Bool, 'alpha': Float % Range(0, 1, inclusive_end=True), 'bypass_tips': Bool}, outputs=[('distance_matrix', DistanceMatrix)], input_descriptions={ 'table': 'The feature table containing the samples over which beta ' 'diversity should be computed.', 'phylogeny': 'Phylogenetic tree containing tip identifiers that ' 'correspond to the feature identifiers in the table. ' 'This tree can contain tip ids that are not present in ' 'the table, but all feature ids in the table must be ' 'present in this tree.' }, parameter_descriptions={ 'metric': 'The beta diversity metric to be computed.', 'threads': threads_description, 'variance_adjusted': 'Perform variance adjustment based on Chang et ' 'al. BMC Bioinformatics 2011. Weights distances ' 'based on the proportion of the relative ' 'abundance represented between the samples at a' ' given node under evaluation.', 'alpha': 'This parameter is only used when the choice of metric is ' 'generalized_unifrac. The value of alpha controls importance' ' of sample proportions. 1.0 is weighted normalized UniFrac.' ' 0.0 is close to unweighted UniFrac, but only if the sample' ' proportions are dichotomized.', 'bypass_tips': 'In a bifurcating tree, the tips make up about 50% of ' 'the nodes in a tree. By ignoring them, specificity ' 'can be traded for reduced compute time. This has the ' 'effect of collapsing the phylogeny, and is analogous ' '(in concept) to moving from 99% to 97% OTUs' }, output_descriptions={'distance_matrix': 'The resulting distance matrix.'}, name='Beta Phylogenetic Passthrough', description="Computes a distance matrix for all pairs of samples in a " "feature table using the unifrac implementation of the " "selected beta diversity metric.", examples={"run on one core (by default)": examples.beta_phylo_passthrough_example, "to run on n cores, replace 1 here with your preferred integer": examples.beta_phylo_passthrough_n_threads_example, "use 'auto' to run on all of host system's available CPU cores": examples.beta_phylo_passthrough_auto_threads_example, "use bypass_tips to trade specificity for reduced compute time": examples.beta_phylo_passthrough_bypass_tips_example, "variance adjustment": examples.beta_phylo_passthrough_variance_adjusted_example, "minimal generalized unifrac": examples.beta_phylo_passthrough_min_generalized_unifrac_example, "generalized unifrac": examples.beta_phylo_passthrough_generalized_unifrac_example, }, citations=[ citations['lozupone2005unifrac'], citations['lozupone2007unifrac'], citations['hamady2010unifrac'], citations['lozupone2011unifrac'], citations['mcdonald2018unifrac'], citations['chang2011variance'], citations['chen2012genUnifrac'], citations['sfiligoi2022unifrac'] ] ) plugin.methods.register_function( function=beta.beta_phylogenetic_meta_passthrough, inputs={'tables': List[FeatureTable[Frequency]], 'phylogenies': List[Phylogeny[Rooted]]}, parameters={'metric': Str % Choices(beta.METRICS['PHYLO']['UNIMPL']), 'threads': Threads, 'variance_adjusted': Bool, 'alpha': Float % Range(0, 1, inclusive_end=True), 'bypass_tips': Bool, 'weights': List[Float], 'consolidation': Str % Choices(list(CONSOLIDATIONS))}, outputs=[('distance_matrix', DistanceMatrix)], input_descriptions={ 'tables': 'The feature tables containing the samples over which beta ' 'diversity should be computed.', 'phylogenies': 'Phylogenetic trees containing tip identifiers that ' 'correspond to the feature identifiers in the table. ' 'This tree can contain tip ids that are not present in ' 'the table, but all feature ids in the table must be ' 'present in this tree.' }, parameter_descriptions={ 'metric': 'The beta diversity metric to be computed.', 'threads': threads_description, 'variance_adjusted': 'Perform variance adjustment based on Chang et ' 'al. BMC Bioinformatics 2011. Weights distances ' 'based on the proportion of the relative ' 'abundance represented between the samples at a' ' given node under evaluation.', 'alpha': 'This parameter is only used when the choice of metric is ' 'generalized_unifrac. The value of alpha controls importance' ' of sample proportions. 1.0 is weighted normalized UniFrac.' ' 0.0 is close to unweighted UniFrac, but only if the sample' ' proportions are dichotomized.', 'bypass_tips': 'In a bifurcating tree, the tips make up about 50% of ' 'the nodes in a tree. By ignoring them, specificity ' 'can be traded for reduced compute time. This has the ' 'effect of collapsing the phylogeny, and is analogous ' '(in concept) to moving from 99% to 97% OTUs', 'weights': 'The weight applied to each tree/table pair. This tuple is ' 'expected to be in index order with tables and phylogenies.' ' Default is to weight each tree/table pair evenly.', 'consolidation': 'The matrix consolidation method, which determines ' 'how the individual distance matrices are ' 'aggregated' }, output_descriptions={'distance_matrix': 'The resulting distance matrix.'}, name='Beta Phylogenetic Meta Passthrough', description="Computes a distance matrix for all pairs of samples in the " "set of feature table and phylogeny pairs, using the unifrac " "implementation of the selected beta diversity metric.", examples={ "Basic meta unifrac": examples.beta_phylo_meta_passthrough_example, "meta with weights": examples.beta_phylo_meta_weights_example, "changing the consolidation method": examples.beta_phylo_meta_consolidation_example, }, citations=[ citations['lozupone2005unifrac'], citations['lozupone2007unifrac'], citations['hamady2010unifrac'], citations['lozupone2011unifrac'], citations['mcdonald2018unifrac'], citations['chang2011variance'], citations['chen2012genUnifrac'], citations['lozupone2008metaunifrac'] ] ) q2-diversity-lib-2024.5.0/q2_diversity_lib/skbio/000077500000000000000000000000001462552567500214545ustar00rootroot00000000000000q2-diversity-lib-2024.5.0/q2_diversity_lib/skbio/LICENSE000066400000000000000000000030721462552567500224630ustar00rootroot00000000000000# sourced from https://github.com/scikit-bio/scikit-bio/blob/main/LICENSE.txt Copyright (c) 2013--, scikit-bio development team. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the names scikit-bio, skbio, or biocore nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. q2-diversity-lib-2024.5.0/q2_diversity_lib/skbio/__init__.py000066400000000000000000000005351462552567500235700ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2013--, scikit-bio development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- q2-diversity-lib-2024.5.0/q2_diversity_lib/skbio/_methods.py000066400000000000000000000041321462552567500236300ustar00rootroot00000000000000import numpy as np from skbio.diversity._util import _validate_counts_vector import skbio.diversity.alpha from scipy.special import gammaln # c&p methods from skbio def _berger_parker(counts): counts = _validate_counts_vector(counts) return counts.max() / counts.sum() def _brillouin_d(counts): counts = _validate_counts_vector(counts) nz = counts[counts.nonzero()] n = nz.sum() return (gammaln(n + 1) - gammaln(nz + 1).sum()) / n def _simpsons_dominance(counts): counts = _validate_counts_vector(counts) return 1 - skbio.diversity.alpha.dominance(counts) def _esty_ci(counts): counts = _validate_counts_vector(counts) f1 = skbio.diversity.alpha.singles(counts) f2 = skbio.diversity.alpha.doubles(counts) n = counts.sum() z = 1.959963985 W = (f1 * (n - f1) + 2 * n * f2) / (n ** 3) return f1 / n - z * np.sqrt(W), f1 / n + z * np.sqrt(W) def _goods_coverage(counts): counts = _validate_counts_vector(counts) f1 = skbio.diversity.alpha.singles(counts) N = counts.sum() return 1 - (f1 / N) def _margalef(counts): counts = _validate_counts_vector(counts) # replaced observed_otu call to sobs return (skbio.diversity.alpha.sobs(counts) - 1) / np.log(counts.sum()) def _mcintosh_d(counts): counts = _validate_counts_vector(counts) u = np.sqrt((counts * counts).sum()) n = counts.sum() return (n - u) / (n - np.sqrt(n)) def _strong(counts): counts = _validate_counts_vector(counts) n = counts.sum() # replaced observed_otu call to sobs s = skbio.diversity.alpha.sobs(counts) i = np.arange(1, len(counts) + 1) sorted_sum = np.sort(counts)[::-1].cumsum() return (sorted_sum / n - (i / s)).max() def _p_evenness(counts): counts = _validate_counts_vector(counts) return _shannon(counts, base=np.e) / np.log( skbio.diversity.alpha.sobs(counts=counts)) def _shannon(counts, base=2): counts = _validate_counts_vector(counts) freqs = counts / counts.sum() nonzero_freqs = freqs[freqs.nonzero()] return -(nonzero_freqs * np.log(nonzero_freqs)).sum() / np.log(base) q2-diversity-lib-2024.5.0/q2_diversity_lib/skbio/test_methods.py000066400000000000000000000057571462552567500245460ustar00rootroot00000000000000import numpy as np import numpy.testing as npt from qiime2.plugin.testing import TestPluginBase from q2_diversity_lib.skbio._methods import (_berger_parker, _brillouin_d, _simpsons_dominance, _esty_ci, _goods_coverage, _margalef, _mcintosh_d, _strong) class SkbioTests(TestPluginBase): package = 'q2_diversity_lib.skbio' # tests for passthrough metrics were sourced from skbio def test_berger_parker_d(self): self.assertEqual(_berger_parker(np.array([5, 5])), 0.5) self.assertEqual(_berger_parker(np.array([1, 1, 1, 1, 0])), 0.25) def test_brillouin_d(self): self.assertAlmostEqual(_brillouin_d(np.array([1, 2, 0, 0, 3, 1])), 0.86289353018248782) def test_esty_ci(self): def _diversity(indices, f): """Calculate diversity index for each window of size 1. indices: vector of indices of taxa f: f(counts) -> diversity measure """ result = [] max_size = max(indices) + 1 freqs = np.zeros(max_size, dtype=int) for i in range(len(indices)): freqs += np.bincount(indices[i:i + 1], minlength=max_size) try: curr = f(freqs) except (ZeroDivisionError, FloatingPointError): curr = 0 result.append(curr) return np.array(result) data = [1, 1, 2, 1, 1, 3, 2, 1, 3, 4] observed_lower, observed_upper = zip(*_diversity(data, _esty_ci)) expected_lower = np.array([1, -1.38590382, -0.73353593, -0.17434465, -0.15060902, -0.04386191, -0.33042054, -0.29041008, -0.43554755, -0.33385652]) expected_upper = np.array([1, 1.38590382, 1.40020259, 0.67434465, 0.55060902, 0.71052858, 0.61613483, 0.54041008, 0.43554755, 0.53385652]) npt.assert_array_almost_equal(observed_lower, expected_lower) npt.assert_array_almost_equal(observed_upper, expected_upper) def test_simpson(self): self.assertAlmostEqual(_simpsons_dominance(np.array([1, 0, 2, 5, 2])), 0.66) self.assertAlmostEqual(_simpsons_dominance(np.array([5])), 0) def test_goods_coverage(self): counts = [1] * 75 + [2, 2, 2, 2, 2, 2, 3, 4, 4] obs = _goods_coverage(counts) self.assertAlmostEqual(obs, 0.23469387755) def test_margalef(self): self.assertEqual(_margalef(np.array([0, 1, 1, 4, 2, 5, 2, 4, 1, 2])), 8 / np.log(22)) def test_mcintosh_d(self): self.assertAlmostEqual(_mcintosh_d(np.array([1, 2, 3])), 0.636061424871458) def test_strong(self): self.assertAlmostEqual(_strong(np.array([1, 2, 3, 1])), 0.214285714) q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/000077500000000000000000000000001462552567500215075ustar00rootroot00000000000000q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/__init__.py000066400000000000000000000005351462552567500236230ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/000077500000000000000000000000001462552567500224205ustar00rootroot00000000000000q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/crawford.biom000066400000000000000000004137411462552567500251110ustar00rootroot00000000000000HDF  `  `TREE`HEAPX observationsample8  @id GCOL No Table IDhttp://biom-format.orgQIIME 1.9.0-rc1, master@2f448392015-01-06T00:37:05.179285199181271766166099 187644 233817 229459 199698 259434260756260205260753336145275819261334194787178735170950260058179063266595312476179069196138275869194978276044 4396877!829401"179719#276260$275563%3392842&259212'270984(262409)4484382*185743+302407,258969-184151.215193/4468234097294125926622592633184567418114152278866174791726901982606539166911:260655;443945<732128=318563>272454?192971@195385A261177B229386C204144D267041E301870F267123G239571H349142I199403J208571K334365L260397M403497N267388O274106P274844Q288931R275136S267411T4338733U4403349V314810W3621189X182033Y1105328Z180105[195005\275627]187233^343581_291750`301012a269902b276985c274438d259228e303479f174754g263908h380534i4397402j335952k1108453l258725m351881n320490o264496p194662q191772r187790s174056t180972u340189v183390w422727x263546y187989z259910{303652|261419}4449524~11518632206226957626066313595626768916299127402144144201571092234121267457267452199534175573259593193463276172197318181419441494259859275707187078270519447113526394627640429358018780726336218321143463742591751816032703851885364407703191398176118167078833390 @type H format-url H format-version@ H generated-by Hcreation-date @ shape@m 0 nnz@-TREEhHEAPX8matrixidsmetadatagroup-metadata SNOD/TREE&HEAPX dataindicesindptr8SNOD(/B-- ?@4 4 deflate-TXTREEx8(-SNOD80x9x^͕=n0 =f!CSir ]^>G4IQҲ<7) 9GdI/J_'G' L$}ʣz('k]u|c?ԓڧGT%靽Jx⹹yVs֩8W}NNM3~OAw޷*S\_DN?ŵiG2'S{}k^ZgY2.O:w-*?߼]NOW''x^m j1 DΞ8I?(ݲJ3u]o_qcz֟wgK߷{._v>jN37b;Y!.;'F?٪OKN>7: ۯIygsXlRwf.]-'io;7}"f}'0._j[j˃Ͼs"8fG ѳ<8Lnjv-0ӉowݗE87w9]O͘mg'7/s=CYOz3ٖ/; |jM|mF8˶n;C< ; X_r;bhr`S[]~7/_1|^Sc7!N<})>jx^svjj61LOL9y&N8q><ANp3u=޿@ %HC:@F2B(ad%;DHE~ P"%┥H%PԢ.OCbhD҂6H':Ӆt;q}G0A e#(F3c<d2d\1,b1KYN<+X*Vc=6"}9ap9YΑy.q\#6w='<9/x+^󆷼go|'RiIO0H&B(ad%D\&|;?/~O4)IEIKz2B( 'M$yK>(@Q)FqJPc-sTREE)I-  deflateH1-T`TREE)-n  deflate:nT`TREE6W+nm deflateCmTXTREEkmGCOL 17380726681616990118299526316544424593104901684221 697874 321484 2120775 268923 17543217033526038719721618652119046044185861865264374042436341176039177427191483343906351794553395178779268755271378 231169!259335"1107945#450047$269359%4331760&182016'275470(270391)187703*191816+353782,163862-319909.307595/2751500272953126999221768863195445434609852750786828435719188783330539167204:259056;233313<164308=263705>178659?174272@310748A132114B336214C550807D262869E178031F173417G316842H376397I190242J182621K174959L259372M175416N847228O461524P350381Q259012R327236S318370T265106U214471V4372578W4127460X274597Y314963Z262166[4417539\100344]170555^261511_273515`177205a839215b4462541c261409d274521e180919f191958g216403h264373i185754j262766k176850l176858m268121n4364243o272812p185222q262399r178926s199307t265641u45363v351859w179181x179188y265828z292745{269378|837473}130335~2598881967774528233434202575787210950194822266483260828348398320635523102126267725924919777518120519619426345227039626310644801763445272753396871851810911871332704912137001268416177802190273191077331965180362258522169398273084326588927066227144943651092766631864972640212647872112006263044311755623341119682533733135495727401818310626160619584026594018124921491926578617466318577717270527442225960931117426858111364434329571330296181344276531180206258250827195206494276580197790f__Lachnospiraceaeg__s__ k__Bacteriap__Deferribacteresc__Deferribactereso__Deferribacteralesf__Deferribacteraceaeg__Mucispirillum s__schaedleri k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Bacteroidaceaeg__Bacteroidess__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes  c__Clostridia o__Clostridiales f__ g__ s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridiales f__Lachnospiraceae!g__"s__# k__Bacteria$ p__Firmicutes% c__Clostridia&o__Clostridiales'f__(g__)s__* k__Bacteria+p__Bacteroidetes,c__Bacteroidia-o__Bacteroidales.f__Bacteroidaceae/g__Bacteroides0s__1 k__Bacteria2 p__Firmicutes3 c__Clostridia4o__Clostridiales5f__Lachnospiraceae6g__7s__x^]eGJE"Ive˚b)[(K%% ٦E5#.ȏ9s9{.3z)kϥ[|EqkSyeO|J>-/|IF-KF彩~AP*_?h~D1|B~G^ +/>nˋ%Ry\^!Wɕ|yZ^#ץkAX7ʟțT~4*o?_;R~_jS%%ky3Bn)[mS2}ѧ '\!'Lp)So1} 'L0} 'L0} 'L0} 'L>a 'afr'L0} 'L0}ѧS)w)>aO>SnO>aѧ ϐOxLp)>s>O}b_˘}/ؗs/ce̾ٗ12f_8þٗ12c_;deryRy=2f_˸Zf_˘}/cerne̾ٗ12}9ٗq˹ٗ12f_˘}s s k4oob&9f9Z,,sĜ7q79obΛ&漉9obΛ&f7q7*hx^=1 F܂BTH`}Ldn.so\;2_F<ź^~srqKα0e~";gt}}.   deflate) T`x^c``` > , @\ {210NE4  deflateY  TXx^a``Qj@WA+rh|y4_HG HEAPXttaxonomy@m deflateuTPTREEG GGCOL k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Bacteroidetes c__Bacteroidia o__Bacteroidales f__Rikenellaceae g__Alistipess__indistinctus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidia o__Bacteroidales!f__Rikenellaceae"g__#s__$ k__Bacteria% p__Firmicutes& c__Clostridia'o__Clostridiales(f__Lachnospiraceae)g__[Ruminococcus]* s__gnavus+ k__Bacteria, p__Firmicutes- c__Clostridia.o__Clostridiales/f__Ruminococcaceae0g__Oscillospira1s__2 k__Bacteria3p__Bacteroidetes4c__Bacteroidia5o__Bacteroidales6f__Bacteroidaceae7g__Bacteroides8s__9 k__Bacteria:p__Bacteroidetes;c__Bacteroidia<o__Bacteroidales=f__S24-7>g__?s__@ k__BacteriaA p__FirmicutesB c__ClostridiaCo__ClostridialesDf__Eg__Fs__G k__BacteriaH p__FirmicutesI c__ClostridiaJo__ClostridialesKf__Lg__Ms__N k__BacteriaO p__FirmicutesP c__ClostridiaQo__ClostridialesRf__Sg__Ts__U k__BacteriaV p__FirmicutesW c__ClostridiaXo__ClostridialesYf__RuminococcaceaeZg__Oscillospira[s__\ k__Bacteria] p__Firmicutes^ c__Clostridia_o__Clostridiales`f__ag__bs__c k__Bacteriadp__Bacteroidetesec__Bacteroidiafo__Bacteroidalesgf__S24-7hg__is__j k__Bacteriak p__Firmicutesl c__Clostridiamo__Clostridialesnf__og__ps__q k__Bacteriar p__Firmicutess c__Clostridiato__Clostridialesuf__Lachnospiraceaevg__ws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__}g__~s__ k__Bacteria p__Firmicutesc__Erysipelotrichio__Erysipelotrichalesf__Erysipelotrichaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Peptococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Clostridiaceaeg__Clostridiums__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Rikenellaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Peptococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceae g__Ruminococcus s__  k__Bacteria  p__Firmicutes  c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacilluss__  k__Bacteria! p__Firmicutes" c__Clostridia#o__Clostridiales$f__%g__&s__' k__Bacteria(p__Bacteroidetes)c__Bacteroidia*o__Bacteroidales+f__Porphyromonadaceae,g__Parabacteroides-s__. k__Bacteria/p__Actinobacteria0c__Coriobacteriia1o__Coriobacteriales2f__Coriobacteriaceae3g__4s__5 k__Bacteria6 p__Firmicutes7 c__Clostridia8o__Clostridiales9f__:g__;s__< k__Bacteria=p__Bacteroidetes>c__Bacteroidia?o__Bacteroidales@f__S24-7Ag__Bs__C k__BacteriaD p__FirmicutesE c__ClostridiaFo__ClostridialesGf__Hg__Is__J k__BacteriaK p__FirmicutesL c__ClostridiaMo__ClostridialesNf__RuminococcaceaeOg__Ps__Q k__BacteriaRp__ActinobacteriaSc__CoriobacteriiaTo__CoriobacterialesUf__CoriobacteriaceaeVg__AdlercreutziaWs__X k__BacteriaY p__FirmicutesZ c__Clostridia[o__Clostridiales\f__]g__^s___ k__Bacteria`p__Bacteroidetesac__Bacteroidiabo__Bacteroidalescf__Bacteroidaceaedg__Bacteroidese s__fragilisf k__Bacteriagp__Bacteroideteshc__Bacteroidiaio__Bacteroidalesjf__Rikenellaceaekg__ls__m k__Bacterian p__Firmicuteso c__Clostridiapo__Clostridialesqf__rg__ss__t k__Bacteriau p__Firmicutesv c__Clostridiawo__Clostridialesxf__yg__zs__{ k__Bacteria|p__Bacteroidetes}c__Bacteroidia~o__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteriap__Proteobacteriac__Epsilonproteobacteriao__Campylobacteralesf__Helicobacteraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes  c__Clostridia o__Clostridiales f__Lachnospiraceae g__ s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Clostridiaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidia o__Bacteroidales!f__S24-7"g__#s__$ k__Bacteria% p__Firmicutes& c__Clostridia'o__Clostridiales(f__)g__*s__+ k__Bacteria, p__Firmicutes- c__Clostridia.o__Clostridiales/f__Ruminococcaceae0g__1s__2 k__Bacteria3 p__Firmicutes4 c__Clostridia5o__Clostridiales6f__7g__8s__9 k__Bacteria: p__Firmicutes;c__Erysipelotrichi<o__Erysipelotrichales=f__Erysipelotrichaceae>g__Clostridium? s__cocleatum@ k__BacteriaA p__FirmicutesB c__ClostridiaCo__ClostridialesDf__Eg__Fs__G k__BacteriaH p__FirmicutesI c__ClostridiaJo__ClostridialesKf__Lg__Ms__N k__BacteriaO p__FirmicutesP c__ClostridiaQo__ClostridialesRf__Sg__Ts__U k__BacteriaV p__FirmicutesW c__ClostridiaXo__ClostridialesYf__Zg__[s__\ k__Bacteria] p__Firmicutes^ c__Clostridia_o__Clostridiales`f__Lachnospiraceaeag__bs__c k__Bacteriad p__Firmicutese c__Clostridiafo__Clostridialesgf__Ruminococcaceaehg__Ruminococcusis__j k__Bacteriak p__Firmicutesl c__Clostridiamo__Clostridialesnf__og__ps__q k__Bacteriar p__Firmicutess c__Clostridiato__Clostridialesuf__vg__ws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__}g__~s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacilluss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Epulopisciums__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Peptococcaceaeg__rc4-4s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__ g__ s__  k__Bacteria  p__Firmicutes  c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Bacilli o__Bacillalesf__Staphylococcaceaeg__Staphylococcuss__  k__Bacteria! p__Firmicutes" c__Clostridia#o__Clostridiales$f__%g__&s__' k__Bacteria( p__Firmicutes) c__Clostridia*o__Clostridiales+f__Ruminococcaceae,g__Ruminococcus-s__. k__Bacteria/ p__Firmicutes0 c__Clostridia1o__Clostridiales2f__3g__4s__5 k__Bacteria6p__Bacteroidetes7c__Bacteroidia8o__Bacteroidales9f__Bacteroidaceae:g__Bacteroides;s__< k__Bacteria= p__Firmicutes> c__Clostridia?o__Clostridiales@f__LachnospiraceaeAg__[Ruminococcus]B s__gnavusC k__BacteriaD p__FirmicutesE c__ClostridiaFo__ClostridialesGf__Hg__Is__J k__BacteriaK p__FirmicutesL c__ClostridiaMo__ClostridialesNf__LachnospiraceaeOg__Ps__Q k__BacteriaR p__FirmicutesS c__ClostridiaTo__ClostridialesU k__BacteriaV p__FirmicutesW c__ClostridiaXo__ClostridialesYf__LachnospiraceaeZg__[s__\ k__Bacteria] p__Firmicutes^ c__Clostridia_o__Clostridiales`f__ag__bs__c k__Bacteriad p__Firmicutese c__Clostridiafo__Clostridialesgf__Lachnospiraceaehg__is__j k__Bacteriakp__Bacteroideteslc__Bacteroidiamo__Bacteroidalesnf__Rikenellaceaeog__ps__q k__Bacteriar p__Firmicutess c__Clostridiato__Clostridialesuf__vg__ws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__Lachnospiraceae}g__~s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__[Odoribacteraceae]g__Odoribacters__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__[Odoribacteraceae]g__Butyricimonass__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__f__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridia o__Clostridiales f__ g__ s__  k__Bacteria p__Firmicutes c__Bacillio__Turicibacteralesf__Turicibacteraceaeg__Turicibacters__ k__Bacteria p__Firmicutesc__Erysipelotrichio__Erysipelotrichalesf__Erysipelotrichaceaeg__Allobaculums__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7 g__!s__" k__Bacteria# p__Firmicutes$ c__Clostridia%o__Clostridiales&f__Lachnospiraceae'g__(s__) k__Bacteria* p__Firmicutes+ c__Clostridia,o__Clostridiales-f__.g__/s__0 k__Bacteria1 p__Firmicutes2 c__Clostridia3o__Clostridiales4f__Peptostreptococcaceae5g__6s__7 k__Bacteria8p__Bacteroidetes9c__Bacteroidia:o__Bacteroidales;f__S24-7<g__=s__> k__Bacteria? p__Firmicutes@ c__ClostridiaAo__ClostridialesBf__Cg__Ds__Es__F k__BacteriaG p__FirmicutesH c__ClostridiaIo__ClostridialesJf__Kg__Ls__M k__BacteriaNp__DeferribacteresOc__DeferribacteresPo__DeferribacteralesQf__DeferribacteraceaeRg__MucispirillumS s__schaedleriT k__BacteriaUp__BacteroidetesVc__BacteroidiaWo__BacteroidalesXf__PorphyromonadaceaeYg__ParabacteroidesZs__[ k__Bacteria\ p__Firmicutes] c__Clostridia^o__Clostridiales_f__`g__as__b k__Bacteriac p__Firmicutesd c__Clostridiaeo__Clostridialesff__Lachnospiraceaegg__hs__i k__Bacteriaj p__Firmicutesk c__Clostridialo__Clostridialesm k__Bacterian p__Firmicuteso c__Clostridiapo__Clostridialesqf__Lachnospiraceaerg__ss__t k__Bacteriau p__Firmicutesv c__Clostridiawo__Clostridialesxf__yg__zs__{ k__Bacteria| p__Firmicutes} c__Clostridia~o__Clostridialesf__g__f__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialeso__Clostridialesf__g__ c__Clostridia p__Firmicutes k__Bacteria k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacilluss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridia o__Clostridiales f__Lachnospiraceae g__[Ruminococcus]  s__gnavus  k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__ g__!s__" k__Bacteria# p__Firmicutes$ c__Clostridia%o__Clostridiales&f__Lachnospiraceae'g__(s__) k__Bacteria* p__Firmicutes+ c__Clostridia,o__Clostridiales-f__.g__/s__0 k__Bacteria1 p__Firmicutes2 c__Clostridia3o__Clostridiales4f__5g__6s__7 k__Bacteria8 p__Firmicutes9 c__Clostridia:o__Clostridiales;f__Ruminococcaceae<g__Oscillospira=s__> k__Bacteria? p__Firmicutes@ c__ClostridiaAo__ClostridialesBf__Cg__Ds__E k__BacteriaF p__FirmicutesG c__ClostridiaHo__ClostridialesIf__Jg__Ks__L k__BacteriaM p__FirmicutesN c__BacilliOo__LactobacillalesPf__StreptococcaceaeQg__StreptococcusRs__S k__BacteriaT p__FirmicutesU c__ClostridiaVo__ClostridialesWf__LachnospiraceaeXg__Ys__Z k__Bacteria[p__Bacteroidetes\c__Bacteroidia]o__Bacteroidales^f__Prevotellaceae_ g__Prevotella`s__a k__Bacteriab p__Firmicutesc c__Bacillid o__Bacillalesef__Staphylococcaceaefg__Staphylococcusg s__sciurih k__Bacteriai p__Firmicutesj c__Clostridiako__Clostridialeslf__Lachnospiraceaemg__ns__o k__Bacteriap p__Firmicutesq c__Clostridiaro__Clostridialessf__Lachnospiraceaetg__Coprococcusus__v k__Bacteriaw p__Firmicutesxc__Erysipelotrichiyo__Erysipelotrichaleszf__Erysipelotrichaceae{g__Allobaculum|s__} k__Bacteria~ p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacilluss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Bacteroidaceaeg__Bacteroides s__eggerthii k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Rikenellaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Rikenellaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__  k__Bacteria  p__Firmicutes  c__Clostridia o__Clostridiales f__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes  c__Clostridia!o__Clostridiales"f__Ruminococcaceae#g__Oscillospira$s__% k__Bacteria& p__Firmicutes' c__Clostridia(o__Clostridiales)f__Lachnospiraceae*g__+s__, k__Bacteria- p__Firmicutes. c__Clostridia/o__Clostridiales0f__Ruminococcaceae1g__Oscillospira2s__3 k__Bacteria4 p__Firmicutes5 c__Clostridia6o__Clostridiales7f__Lachnospiraceae8g__[Ruminococcus]9 s__gnavus: k__Bacteria; p__Firmicutes< c__Clostridia=o__Clostridiales>f__?g__@s__A k__BacteriaBp__BacteroidetesCc__BacteroidiaDo__BacteroidalesEf__BacteroidaceaeFg__BacteroidesGs__H k__BacteriaI p__FirmicutesJ c__ClostridiaKo__ClostridialesLf__Mg__Ns__O k__BacteriaP p__FirmicutesQ c__ClostridiaRo__ClostridialesSf__RuminococcaceaeTg__Us__V k__BacteriaW p__FirmicutesX c__ClostridiaYo__ClostridialesZf__Ruminococcaceae[g__Oscillospira\s__] k__Bacteria^ p__Firmicutes_ c__Clostridia`o__Clostridialesaf__bg__cs__d k__Bacteriae p__Firmicutesfc__Erysipelotrichigo__Erysipelotrichaleshf__Erysipelotrichaceaeig__Coprobacillusjs__k k__Bacterial p__Firmicutesm c__Clostridiano__Clostridialesof__pg__qs__r k__Bacterias p__Firmicutest c__Clostridiauo__Clostridialesvf__Ruminococcaceaewg__Oscillospiraxs__y k__Bacteriazp__Bacteroidetes{c__Bacteroidia|o__Bacteroidales}f__S24-7~g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__[Paraprevotellaceae]g__[Prevotella]s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__TM7c__TM7-3o__CW040f__F16g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Porphyromonadaceaeg__Parabacteroidess__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Proteobacteriac__Deltaproteobacteriao__Desulfovibrionalesf__Desulfovibrionaceaeg__Desulfovibrio s__C21_c20 k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__[Odoribacteraceae]g__Odoribacters__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridiales f__ g__ s__  k__Bacteria  p__Firmicutes c__Clostridiao__Clostridialesf__g__s__f__Bacteroidaceaeg__Bacteroides s__caccae k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridia o__Clostridiales!f__"g__#s__$ k__Bacteria%p__Bacteroidetes&c__Bacteroidia'o__Bacteroidales(f__S24-7)g__*s__+ k__Bacteria, p__Firmicutes- c__Clostridia.o__Clostridiales/f__Ruminococcaceae0g__Oscillospira1s__2 k__Bacteria3 p__Firmicutes4 c__Clostridia5o__Clostridiales6f__7g__8s__9 k__Bacteria: p__Firmicutes; c__Bacilli<o__Lactobacillales=f__Lactobacillaceae>g__Lactobacillus?s__@ k__BacteriaA p__FirmicutesB c__ClostridiaCo__ClostridialesDf__LachnospiraceaeEg__[Ruminococcus]F s__gnavusG k__BacteriaH p__FirmicutesI c__ClostridiaJo__ClostridialesKf__Lg__Ms__N k__BacteriaO p__FirmicutesP c__ClostridiaQo__ClostridialesRf__Sg__Ts__U k__BacteriaV p__FirmicutesW c__ClostridiaXo__ClostridialesYf__LachnospiraceaeZg__[s__\ k__Bacteria] p__Firmicutes^ c__Clostridia_o__Clostridiales`f__Lachnospiraceaeag__bs__c k__Bacteriad p__Firmicutese c__Clostridiafo__Clostridialesgf__hg__is__j k__Bacteriak p__Firmicutesl c__Clostridiamo__Clostridialesnf__Lachnospiraceaeog__[Ruminococcus]p s__gnavusq k__Bacteriarp__Bacteroidetessc__Bacteroidiato__Bacteroidalesuf__S24-7vg__ws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__}g__~s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Clostridiaceaeg__s__ k__Bacteriap__Actinobacteriac__Coriobacteriiao__Coriobacterialesf__Coriobacteriaceaeg__Adlercreutzias__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacilluss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteriap__Actinobacteriac__Coriobacteriiao__Coriobacterialesf__Coriobacteriaceaeg__Adlercreutzias__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutesc__Erysipelotrichio__Erysipelotrichalesf__Erysipelotrichaceaeg__Allobaculums__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacillus s__reuteri k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__ g__ s__  k__Bacteria p__Bacteroidetes c__Bacteroidiao__Bacteroidalesf__Bacteroidaceaeg__Bacteroides s__fragilis k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__  k__Bacteria! p__Firmicutes" c__Clostridia#o__Clostridiales$f__%g__&s__' k__Bacteria(p__Bacteroidetes)c__Bacteroidia*o__Bacteroidales+f__S24-7,g__-s__. k__Bacteria/ p__Firmicutes0c__Erysipelotrichi1o__Erysipelotrichales2f__Erysipelotrichaceae3g__Allobaculum4s__5 k__Bacteria6 p__Firmicutes7 c__Bacilli8o__Lactobacillales9f__Lactobacillaceae:g__Lactobacillus;s__< k__Bacteria= p__Firmicutes> c__Clostridia?o__Clostridiales@f__Ag__Bs__C k__BacteriaD p__FirmicutesE c__ClostridiaFo__ClostridialesGf__Hg__Is__J k__BacteriaK p__FirmicutesL c__ClostridiaMo__ClostridialesNf__Og__Ps__Qs__R k__BacteriaS p__FirmicutesT c__ClostridiaUo__ClostridialesVf__Wg__Xs__Y k__BacteriaZp__Bacteroidetes[c__Bacteroidia\o__Bacteroidales]f__S24-7^g___s__` k__Bacteriaa p__Firmicutesb c__Clostridiaco__Clostridialesdf__Ruminococcaceaeeg__fs__g k__Bacteriah p__Firmicutesic__Erysipelotrichijo__Erysipelotrichaleskf__Erysipelotrichaceaelg__[Eubacterium]m s__dolichumn k__Bacteriao p__Firmicutesp c__Clostridiaqo__Clostridialesrf__GCOL@g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__ s__ k__Bacteria p__Firmicutes c__Clostridia o__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes! c__Clostridia"o__Clostridiales#f__Clostridiaceae$g__%s__& k__Bacteria'p__Bacteroidetes(c__Bacteroidia)o__Bacteroidales*f__S24-7+g__,s__- k__Bacteria. p__Firmicutes/ c__Clostridia0o__Clostridiales1f__2g__3s__4 k__Bacteria5 p__Firmicutes6 c__Clostridia7o__Clostridiales8f__Ruminococcaceae9g__Oscillospira:s__; k__Bacteria< p__Firmicutes= c__Clostridia>o__Clostridiales?f__@g__As__B k__BacteriaC p__FirmicutesD c__ClostridiaEo__ClostridialesFf__LachnospiraceaeGg__Hs__I k__BacteriaJ p__FirmicutesK c__ClostridiaLo__ClostridialesMf__LachnospiraceaeNg__[Ruminococcus]O s__gnavusP k__BacteriaQ p__FirmicutesR c__ClostridiaSo__ClostridialesTf__LachnospiraceaeUg__Vs__W k__BacteriaX p__FirmicutesY c__ClostridiaZo__Clostridiales[f__Ruminococcaceae\g__]s__^ k__Bacteria_p__Bacteroidetes`c__Bacteroidiaao__Bacteroidalesbf__Bacteroidaceaecg__Bacteroidesds__e k__Bacteriaf p__Firmicutesg c__Clostridiaho__Clostridialesif__Lachnospiraceaejg__[Ruminococcus]k s__gnavusl k__Bacteriam p__Firmicutesnc__Erysipelotrichioo__Erysipelotrichalespf__Erysipelotrichaceaeqg__rs__s k__Bacteriat p__Firmicutesu c__Clostridiavo__Clostridialeswf__Lachnospiraceaexg__[Ruminococcus]y s__gnavusz k__Bacteria{p__Bacteroidetes|c__Bacteroidia}o__Bacteroidales~o__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Actinobacteriac__Coriobacteriiao__Coriobacterialesf__Coriobacteriaceaeg__Adlercreutzias__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridias__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridiales f__ o__Clostridiales f__ g__ s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__ c__Clostridia p__Firmicutes k__Bacteria 10084.PC.636 10084.PC.635  10084.PC.607! 10084.PC.634" 10084.PC.355# 10084.PC.354$ 10084.PC.356% 10084.PC.593& 10084.PC.481(SNODptx^]uUUGsݍ(aw "vR**w7Ys}gLaf'g 1NGq W¿ 5/@o![R2rF?<1N8ǃ'7IMo*xx:z3 l&59%zzqF8pßd%D/c41q8}aˀ^F2[g8k>ˎ^8=_9;I=^p>c+(z/.^IJKˠWrUqV+_ \*UW Հ&z:Ճ>!z"ww5Oz >Go?< ~QEo\D&'7i͈|΄x6zs |%-t#+[*Wנu mx3z0x x+zh1oo@ogލ= ދS=a};A!wX&3돡w{>_}Eױzo6ޅ=S}}/_Bkw}>>_D|?/]> |" Mnw|)^PYYCeq:Ή^.rz>`=o3+F|E+Ip)J e+pE*W\\jWsP?O z׎qJWz_Cp#pc B5mmk_{ppG:uw@'~  xz|i(zHF7<<qO@o"|SЛ 4tf7< <9Co>| [ PKC?:\YJVo@o#{}8a z[{GC|ށNgx7zO`O¿B0GG{g;Is= /2|B_^ 轅 ~wC_ yxS}>F,zi3/_5 [{9y."zg~2 zWC~zwޭP?zAwO]o?{݇_?A3Eڋԟ8^T襎tiO N^z2DgzHggE/[XosEzF/zy W\\"CxׯDgIJW2蕃<"zRU#^ѫ^HS \:Ϻ׋q2F5777C9--kZvw@-C')i%G? |? OjǛ>/M}_oߚ3>g;o M{L}?Z?agy/~5]u7ӯaڻiڻezn~7=5eiiᄅ_Ӟa(^\Dā B/5iiK_zppF2eg@/' >p~ _\C8|%}~KbT{i|}ʢW)^@g%+Kx= ^8818i}ǽ~~й[\>kݹ&}v>f~7l\ܦ3 ?,n+a/ .e+m+cz5_9^yS_YLSߴWմWWk`Z:i!i)i%i i=i3inaimc o`hz`s(xio~FGFFǀǚƙǃ'&^I)i馽Y٦=/?}|ӿ/X`_^d[l[^jeU5_g_`hl{0x xiSL{Lv]ݦ'L{O2? goz1ϳc㦽L{ϛ^~ɴ𫦽L}i{6޻?0y?>ms61}r6wִ0oK`7ou7o|m~ynݼܼ7]0yM{nܼ?n?nܼynܼ]3]7yiڻezܼg7o~7os6m>ys6m>?0s6m>ys6m>ys6nu6m~|m~ϫ=7o{nݼg7o{nܼg7o~7oa?nm~=7o|>7o|v6mmGݼ߿=7o{nn泛ߧyw6ݼyܼg7oWjՄV6y.k47B1|MMk pKZz -z?ڃ;zt ~^b?_Op/po7c |P~FG7*7<O@o"ћT4tf739 }?<B-} zK[<|EJ*WנY^m6¿ =~zF ܃ޟO4xz0(z,z;> ~"z/z +W{ o&|o޻~| |?OzKk7o;]GO /+]W5z~&z л@_? t= ~CpǁKRp2B/5iiK_zppIx^$iFΈȶm۶m۶axm۶m۶ml|9=_ս~Qzcي/UXa"S0N)j'''#ßB/6qqы_|p3sSߋȏa"?aִt`^? 8*='B.C_4>So;`;h~t?s|Ҵw*#~653_0]4]__1]5__706137qiiﱩ i+S@oL{ߚo7<i'S_L{o4exioxG0 GE~$ Ѧ3ƴ7ִ7t 9lڛbڛjnڛa mڛc gڛo[^^d[l_^j[f[^^i[e_ ^c[k[^`hd<ڌ-ϣm]Xmc?? r}h_}MMk_sh zKh 5 zm1O;p{:C w3z] _HN#9q7z}}?^/ (g`-;ק kӴX _Ih8׆p<{gQx/pn pNbKjONnKaKiONmKcO NgKoLL{M}YLgY~ԟӴt<|B¦"篨L{M{%L%L{M}eeL{MM{Lljګfaګiګez6}1575054i)ii%i-ۙ_{|}^G^'y:ʹ0426}, r_y>h:G|i3S i!h <*99'IIP/ɼ~9988%zO N^ZҁӃ3LOfgϬggG/ ΅^nO^ㅞA΋^>_\BEŽl!.yJz\ S2E<|Я룒U*zq55Ы p]WF77A 资5zmk ;p{: uwwC;==z{z}^?{z?<}y>'Sg}/_717X}yD'OzC<>'/yz5#p<#h3XOoћD&7<Ot?{L|,l3㙇棷,/o1[g)z[ JVuo mFo z[ގvxv¿ =Co?|; Qw||SA,|л%exz |B6|wwлCg?~{5 zo{~_ޏGQvT|x~yы_Lޟba=_qqyȯد%E/)K^*̗4襅?8=zΌ^xa=_Np.rc<~ݯ~}/?z+^a^_ԯb~8z%+^)piW\\ WWB2|UUЫ pMjW\\a> >>G?<Ƨ>gx̳ߧOp<;Q􎁏wF g;E%.|kO/7л-ow{?@!{S3s^ҧWX߂ߡ $#Eq;\=h{b 8E/N^B9zc=/)KyRS4t襇?8#zggE/9 _.pnpŸFE+~pYR蕆 ,z+^%+WjՄڎ_Guq<Ы^&77C9|-z+Gq|mk^;|}{G;:~y';;z^WcgO >C?zO}SG/ /7}=|E;G #?FP/0G7QXm,~GodT4?<YEo ["b¿ ^/+}Bo5zk[_ ooBo3 6ww =ut?9;Awc蝀$O| ]%G_ U97л -m|^/~:ϰ|K+^c7|ppU_TWaqu>~G/D@/r8Ņ?8>z KNN^py՟S4C/eD/Yˊ^6s\A//ˇ^~ _\quC8%%+/ ..^9ʻz2zUЫyWZՁ.z]= on^c ^-k^+[۠vuvqۮ8nuGz= _p_p??zb\= z/ <17o}A Gv&3Ŀzc?C ߟ7! (hǺџD$&SSћ27͂o6曃o?-<[RJW0juu<ۄ ފ6w=x?z;>>wS蝆 ,z\__D|]}]UWk@&|~]{~cr -z>"QQ>vz~cXŁ/.88?l^TREEHEAPXiPTREEHEAPX8matrixidsmetadatagroup-metadata TREEHEAPX dataindicesindptr8SNOD(!prYs- ?@4 4 deflateq-TXTREE{o-SNODa:q-  deflate-T`TREE)-x^eee 1Cwwwww7؝ݭ`+` &vw5v(a󭗵q9<$BICyTBMBmC4,2Cq*q62\57v܍f<&(' _'l? J>(b(C>Zz/acc-y>Qh>AX}qĹrp-n:܌;^Ż?w9GOP 1:9ǒ{$]eQ}Ǟ -=C0#1Kq1rYNYxiH7oyeQQՒX7eh腁Q ss)."=r3No`>șcɽ?49r߆p<徿WP}+ wTz<1݈9 Ÿܑw$b9V&Ϡ(JN4BSy]{?LdL{qf__Lachnospiraceae?g__@s__A k__BacteriaB p__FirmicutesC c__ClostridiaDo__ClostridialesEf__Fg__Gs__H k__BacteriaI p__FirmicutesJ c__ClostridiaKo__ClostridialesLf__Mg__Ns__O k__BacteriaP p__FirmicutesQ c__ClostridiaRo__ClostridialesSf__Tg__Us__V k__BacteriaW p__FirmicutesX c__ClostridiaYo__ClostridialesZf__Lachnospiraceae[g__\s__] k__Bacteria^ p__Firmicutes_ c__Clostridia`o__Clostridialesaf__bg__cs__d k__Bacteriaep__Bacteroidetesfc__Bacteroidiago__Bacteroidaleshf__Bacteroidaceaeig__Bacteroidesjs__k k__Bacterial p__Firmicutesm c__Clostridiano__Clostridialesof__Lachnospiraceaepg__qs__r k__Bacterias p__Firmicutest c__Clostridiauo__Clostridialesvf__wg__xs__y k__Bacteriazp__Bacteroidetes{c__Bacteroidia|o__Bacteroidales}f__Rikenellaceae~ g__Alistipess__indistinctus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__HEAPX8 metadatagroup-metadatamatrixids SNODPz;|TREEIHEAPX(taxonomy@SNODXJJL0 y(M@M`O(mm deflateTA^@TREE`Qp_GCOL k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Bacteroidetes c__Bacteroidia o__Bacteroidales f__Rikenellaceae g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteriap__Bacteroidetesc__Bacteroidia o__Bacteroidales!f__Bacteroidaceae"g__Bacteroides#s__$ k__Bacteria%p__Bacteroidetes&c__Bacteroidia'o__Bacteroidales(f__S24-7)g__*s__+ k__Bacteria, p__Firmicutes- c__Clostridia.o__Clostridiales/f__0g__1s__2 k__Bacteria3 p__Firmicutes4 c__Clostridia5o__Clostridiales6f__7g__8s__9 k__Bacteria: p__Firmicutes; c__Clostridia<o__Clostridiales=f__>g__?s__@ k__BacteriaA p__FirmicutesB c__ClostridiaCo__ClostridialesDf__RuminococcaceaeEg__OscillospiraFs__G k__BacteriaH p__FirmicutesI c__ClostridiaJo__ClostridialesKf__Lg__Ms__N k__BacteriaOp__BacteroidetesPc__BacteroidiaQo__BacteroidalesRf__S24-7Sg__Ts__U k__BacteriaV p__FirmicutesW c__ClostridiaXo__ClostridialesYf__Zg__[s__\ k__Bacteria] p__Firmicutes^ c__Clostridia_o__Clostridiales`f__Lachnospiraceaeag__bs__c k__Bacteriad p__Firmicutese c__Clostridiafo__Clostridialesgf__hg__is__j k__Bacteriak p__Firmicuteslc__Erysipelotrichimo__Erysipelotrichalesnf__Erysipelotrichaceaeog__ps__q k__Bacteriar p__Firmicutess c__Clostridiato__Clostridialesuf__vg__ws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__Lachnospiraceae}g__~s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Peptococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Clostridiaceaeg__Clostridiums__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Rikenellaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Peptococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceae g__Lactobacillus s__  k__Bacteria  p__Firmicutes  c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Porphyromonadaceaeg__Parabacteroidess__ k__Bacteriap__Actinobacteriac__Coriobacteriiao__Coriobacterialesf__Coriobacteriaceaeg__s__  k__Bacteria! p__Firmicutes" c__Clostridia#o__Clostridiales$f__%g__&s__' k__Bacteria(p__Bacteroidetes)c__Bacteroidia*o__Bacteroidales+f__S24-7,g__-s__. k__Bacteria/ p__Firmicutes0 c__Clostridia1o__Clostridiales2f__3g__4s__5 k__Bacteria6 p__Firmicutes7 c__Clostridia8o__Clostridiales9f__Ruminococcaceae:g__;s__< k__Bacteria=p__Actinobacteria>c__Coriobacteriia?o__Coriobacteriales@f__CoriobacteriaceaeAg__AdlercreutziaBs__C k__BacteriaD p__FirmicutesE c__ClostridiaFo__ClostridialesGf__Hg__Is__J k__BacteriaKp__BacteroidetesLc__BacteroidiaMo__BacteroidalesNf__BacteroidaceaeOg__BacteroidesP s__fragilisQ k__BacteriaRp__BacteroidetesSc__BacteroidiaTo__BacteroidalesUf__RikenellaceaeVg__Ws__X k__BacteriaY p__FirmicutesZ c__Clostridia[o__Clostridiales\f__]g__^s___ k__Bacteria` p__Firmicutesa c__Clostridiabo__Clostridialescf__dg__es__f k__Bacteriagp__Bacteroideteshc__Bacteroidiaio__Bacteroidalesjf__S24-7kg__ls__m k__Bacterian p__Firmicuteso c__Clostridiapo__Clostridialesqf__Ruminococcaceaerg__Ruminococcusss__t k__Bacteriaup__Proteobacteriavc__Epsilonproteobacteriawo__Campylobacteralesxf__Helicobacteraceaeyg__zs__{ k__Bacteria| p__Firmicutes} c__Clostridia~o__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Clostridiaceaeg__s__ k__Bacteria p__Bacteroidetes c__Bacteroidia o__Bacteroidales f__S24-7 g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridia o__Clostridiales!f__"g__#s__$ k__Bacteria% p__Firmicutes&c__Erysipelotrichi'o__Erysipelotrichales(f__Erysipelotrichaceae)g__Clostridium* s__cocleatum+ k__Bacteria, p__Firmicutes- c__Clostridia.o__Clostridiales/f__0g__1s__2 k__Bacteria3 p__Firmicutes4 c__Clostridia5o__Clostridiales6f__7g__8s__9 k__Bacteria: p__Firmicutes; c__Clostridia<o__Clostridiales=f__>g__?s__@ k__BacteriaA p__FirmicutesB c__ClostridiaCo__ClostridialesDf__Eg__Fs__G k__BacteriaH p__FirmicutesI c__ClostridiaJo__ClostridialesKf__LachnospiraceaeLg__Ms__N k__BacteriaO p__FirmicutesP c__ClostridiaQo__ClostridialesRf__RuminococcaceaeSg__RuminococcusTs__U k__BacteriaV p__FirmicutesW c__ClostridiaXo__ClostridialesYf__Zg__[s__\ k__Bacteria] p__Firmicutes^ c__Clostridia_o__Clostridiales`f__ag__bs__c k__Bacteriad p__Firmicutese c__Clostridiafo__Clostridialesgf__hg__is__j k__Bacteriak p__Firmicutesl c__Clostridiamo__Clostridialesnf__og__ps__q k__Bacteriar p__Firmicutess c__Bacillito__Lactobacillalesuf__Lactobacillaceaevg__Lactobacillusws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__}g__~s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Epulopisciums__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Peptococcaceaeg__rc4-4s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Bacilli o__Bacillalesf__Staphylococcaceae g__Staphylococcus s__  k__Bacteria  p__Firmicutes  c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__  k__Bacteria!p__Bacteroidetes"c__Bacteroidia#o__Bacteroidales$f__Bacteroidaceae%g__Bacteroides&s__' k__Bacteria( p__Firmicutes) c__Clostridia*o__Clostridiales+f__Lachnospiraceae,g__[Ruminococcus]- s__gnavus. k__Bacteria/ p__Firmicutes0 c__Clostridia1o__Clostridiales2f__3g__4s__5 k__Bacteria6 p__Firmicutes7 c__Clostridia8o__Clostridiales9f__Lachnospiraceae:g__;s__< k__Bacteria= p__Firmicutes> c__Clostridia?o__Clostridiales@ k__BacteriaA p__FirmicutesB c__ClostridiaCo__ClostridialesDf__LachnospiraceaeEg__Fs__G k__BacteriaH p__FirmicutesI c__ClostridiaJo__ClostridialesKf__Lg__Ms__N k__BacteriaO p__FirmicutesP c__ClostridiaQo__ClostridialesRf__LachnospiraceaeSg__Ts__U k__BacteriaVp__BacteroidetesWc__BacteroidiaXo__BacteroidalesYf__RikenellaceaeZg__[s__\ k__Bacteria] p__Firmicutes^ c__Clostridia_o__Clostridiales`f__ag__bs__c k__Bacteriad p__Firmicutese c__Clostridiafo__Clostridialesgf__Lachnospiraceaehg__is__j k__Bacteriak p__Firmicutesl c__Clostridiamo__Clostridialesnf__og__ps__q k__Bacteriar p__Firmicutess c__Clostridiato__Clostridialesuf__Lachnospiraceaevg__Coprococcusws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__}g__~s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__[Odoribacteraceae]g__Odoribacters__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__[Odoribacteraceae]g__Butyricimonass__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__f__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Bacillio__Turicibacteralesf__Turicibacteraceaeg__Turicibacters__ k__Bacteria p__Firmicutesc__Erysipelotrichio__Erysipelotrichalesf__Erysipelotrichaceaeg__Allobaculums__ k__Bacteriap__Bacteroidetesc__Bacteroidia o__Bacteroidales f__S24-7 g__ s__  k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Peptostreptococcaceae g__!s__" k__Bacteria#p__Bacteroidetes$c__Bacteroidia%o__Bacteroidales&f__S24-7'g__(s__) k__Bacteria* p__Firmicutes+ c__Clostridia,o__Clostridiales-f__.g__/s__0s__1 k__Bacteria2 p__Firmicutes3 c__Clostridia4o__Clostridiales5f__6g__7s__8 k__Bacteria9p__Deferribacteres:c__Deferribacteres;o__Deferribacterales<f__Deferribacteraceae=g__Mucispirillum> s__schaedleri? k__Bacteria@p__BacteroidetesAc__BacteroidiaBo__BacteroidalesCf__PorphyromonadaceaeDg__ParabacteroidesEs__F k__BacteriaG p__FirmicutesH c__ClostridiaIo__ClostridialesJf__Kg__Ls__M k__BacteriaN p__FirmicutesO c__ClostridiaPo__ClostridialesQf__LachnospiraceaeRg__Ss__T k__BacteriaU p__FirmicutesV c__ClostridiaWo__ClostridialesX k__BacteriaY p__FirmicutesZ c__Clostridia[o__Clostridiales\f__Lachnospiraceae]g__^s___ k__Bacteria` p__Firmicutesa c__Clostridiabo__Clostridialescf__dg__es__f k__Bacteriag p__Firmicutesh c__Clostridiaio__Clostridialesjf__kg__lf__mg__ns__o k__Bacteriap p__Firmicutesq c__Clostridiaro__Clostridialessf__tg__us__vs__w k__Bacteriax p__Firmicutesy c__Clostridiazo__Clostridiales{o__Clostridiales|f__}g__~ c__Clostridia p__Firmicutes k__Bacteria k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacilluss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridia o__Clostridiales f__ g__ s__  k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__ g__!s__" k__Bacteria# p__Firmicutes$ c__Clostridia%o__Clostridiales&f__Ruminococcaceae'g__Oscillospira(s__) k__Bacteria* p__Firmicutes+ c__Clostridia,o__Clostridiales-f__.g__/s__0 k__Bacteria1 p__Firmicutes2 c__Clostridia3o__Clostridiales4f__5g__6s__7 k__Bacteria8 p__Firmicutes9 c__Bacilli:o__Lactobacillales;f__Streptococcaceae<g__Streptococcus=s__> k__Bacteria? p__Firmicutes@ c__ClostridiaAo__ClostridialesBf__LachnospiraceaeCg__Ds__E k__BacteriaFp__BacteroidetesGc__BacteroidiaHo__BacteroidalesIf__PrevotellaceaeJ g__PrevotellaKs__L k__BacteriaM p__FirmicutesN c__BacilliO o__BacillalesPf__StaphylococcaceaeQg__StaphylococcusR s__sciuriS k__BacteriaT p__FirmicutesU c__ClostridiaVo__ClostridialesWf__LachnospiraceaeXg__Ys__Z k__Bacteria[ p__Firmicutes\ c__Clostridia]o__Clostridiales^f__Lachnospiraceae_g__Coprococcus`s__a k__Bacteriab p__Firmicutescc__Erysipelotrichido__Erysipelotrichalesef__Erysipelotrichaceaefg__Allobaculumgs__h k__Bacteriai p__Firmicutesj c__Bacilliko__Lactobacillaleslf__Lactobacillaceaemg__Lactobacillusns__o k__Bacteriap p__Firmicutesq c__Clostridiaro__Clostridialessf__Ruminococcaceaetg__us__v k__Bacteriaw p__Firmicutesx c__Clostridiayo__Clostridialeszf__Ruminococcaceae{g__|s__} k__Bacteria~ p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Bacteroidaceaeg__Bacteroides s__eggerthii k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Rikenellaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Rikenellaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus  k__Bacteria  p__Firmicutes  c__Clostridia o__Clostridiales f__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes  c__Clostridia!o__Clostridiales"f__Lachnospiraceae#g__[Ruminococcus]$ s__gnavus% k__Bacteria& p__Firmicutes' c__Clostridia(o__Clostridiales)f__*g__+s__, k__Bacteria-p__Bacteroidetes.c__Bacteroidia/o__Bacteroidales0f__Bacteroidaceae1g__Bacteroides2s__3 k__Bacteria4 p__Firmicutes5 c__Clostridia6o__Clostridiales7f__8g__9s__: k__Bacteria; p__Firmicutes< c__Clostridia=o__Clostridiales>f__Ruminococcaceae?g__@s__A k__BacteriaB p__FirmicutesC c__ClostridiaDo__ClostridialesEf__RuminococcaceaeFg__OscillospiraGs__H k__BacteriaI p__FirmicutesJ c__ClostridiaKo__ClostridialesLf__Mg__Ns__O k__BacteriaP p__FirmicutesQc__ErysipelotrichiRo__ErysipelotrichalesSf__ErysipelotrichaceaeTg__CoprobacillusUs__V k__BacteriaW p__FirmicutesX c__ClostridiaYo__ClostridialesZf__[g__\s__] k__Bacteria^ p__Firmicutes_ c__Clostridia`o__Clostridialesaf__Ruminococcaceaebg__Oscillospiracs__d k__Bacteriaep__Bacteroidetesfc__Bacteroidiago__Bacteroidaleshf__S24-7ig__js__k k__Bacterial p__Firmicutesm c__Clostridiano__Clostridialesof__Lachnospiraceaepg__qs__r k__Bacteriasp__Bacteroidetestc__Bacteroidiauo__Bacteroidalesvf__[Paraprevotellaceae]wg__[Prevotella]xs__y k__Bacteriaz p__Firmicutes{ c__Clostridia|o__Clostridiales}f__~g__s__ k__Bacteriap__TM7c__TM7-3o__CW040f__F16g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Porphyromonadaceaeg__Parabacteroidess__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Proteobacteriac__Deltaproteobacteriao__Desulfovibrionalesf__Desulfovibrionaceaeg__Desulfovibrio s__C21_c20 k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__[Odoribacteraceae]g__Odoribacters__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__f__Bacteroidaceaeg__Bacteroides s__caccae k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria  p__Firmicutes  c__Clostridia o__Clostridiales f__ g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridia o__Clostridiales!f__"g__#s__$ k__Bacteria% p__Firmicutes& c__Bacilli'o__Lactobacillales(f__Lactobacillaceae)g__Lactobacillus*s__+ k__Bacteria, p__Firmicutes- c__Clostridia.o__Clostridiales/f__Lachnospiraceae0g__[Ruminococcus]1 s__gnavus2 k__Bacteria3 p__Firmicutes4 c__Clostridia5o__Clostridiales6f__7g__8s__9 k__Bacteria: p__Firmicutes; c__Clostridia<o__Clostridiales=f__>g__?s__@ k__BacteriaA p__FirmicutesB c__ClostridiaCo__ClostridialesDf__LachnospiraceaeEg__Fs__G k__BacteriaH p__FirmicutesI c__ClostridiaJo__ClostridialesKf__LachnospiraceaeLg__Ms__N k__BacteriaO p__FirmicutesP c__ClostridiaQo__ClostridialesRf__Sg__Ts__U k__BacteriaV p__FirmicutesW c__ClostridiaXo__ClostridialesYf__LachnospiraceaeZg__[Ruminococcus][ s__gnavus\ k__Bacteria]p__Bacteroidetes^c__Bacteroidia_o__Bacteroidales`f__S24-7ag__bs__c k__Bacteriad p__Firmicutese c__Clostridiafo__Clostridialesgf__hg__is__j k__Bacteriakp__Bacteroideteslc__Bacteroidiamo__Bacteroidalesnf__S24-7og__ps__q k__Bacteriar p__Firmicutess c__Clostridiato__Clostridialesuf__Ruminococcaceaevg__ws__x k__Bacteriay p__Firmicutesz c__Clostridia{o__Clostridiales|f__Clostridiaceae}g__~s__ k__Bacteriap__Actinobacteriac__Coriobacteriiao__Coriobacterialesf__Coriobacteriaceaeg__Adlercreutzias__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacilluss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__[Ruminococcus] s__gnavus k__Bacteriap__Actinobacteriac__Coriobacteriiao__Coriobacterialesf__Coriobacteriaceaeg__Adlercreutzias__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__Coprococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutesc__Erysipelotrichio__Erysipelotrichalesf__Erysipelotrichaceaeg__Allobaculums__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Bacillio__Lactobacillalesf__Lactobacillaceaeg__Lactobacillus s__reuteri k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__Bacteroidaceaeg__Bacteroides s__fragilis k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceae g__Coprococcus s__  k__Bacteria  p__Firmicutes  c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutesc__Erysipelotrichio__Erysipelotrichalesf__Erysipelotrichaceaeg__Allobaculums__  k__Bacteria! p__Firmicutes" c__Bacilli#o__Lactobacillales$f__Lactobacillaceae%g__Lactobacillus&s__' k__Bacteria( p__Firmicutes) c__Clostridia*o__Clostridiales+f__,g__-s__. k__Bacteria/ p__Firmicutes0 c__Clostridia1o__Clostridiales2f__3g__4s__5 k__Bacteria6 p__Firmicutes7 c__Clostridia8o__Clostridiales9f__:g__;s__<s__= k__Bacteria> p__Firmicutes? c__Clostridia@o__ClostridialesAf__Bg__Cs__D k__BacteriaEp__BacteroidetesFc__BacteroidiaGo__BacteroidalesHf__S24-7Ig__Js__K k__BacteriaL p__FirmicutesM c__ClostridiaNo__ClostridialesOf__RuminococcaceaePg__Qs__R k__BacteriaS p__FirmicutesTc__ErysipelotrichiUo__ErysipelotrichalesVf__ErysipelotrichaceaeWg__[Eubacterium]X s__dolichumY k__BacteriaZ p__Firmicutes[ c__Clostridia\o__Clostridiales]f__^g___s__` k__Bacteriaa p__Firmicutesb c__Clostridiaco__Clostridialesdf__eg__fs__g k__Bacteriah p__Firmicutesi c__Clostridiajo__Clostridialeskf__Lachnospiraceaelg__[Ruminococcus]m s__gnavusn k__Bacteriao p__Firmicutesp c__Clostridiaqo__ClostridialesGCOL f__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceae g__ s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Clostridiaceaeg__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria! p__Firmicutes" c__Clostridia#o__Clostridiales$f__Ruminococcaceae%g__Oscillospira&s__' k__Bacteria( p__Firmicutes) c__Clostridia*o__Clostridiales+f__,g__-s__. k__Bacteria/ p__Firmicutes0 c__Clostridia1o__Clostridiales2f__Lachnospiraceae3g__4s__5 k__Bacteria6 p__Firmicutes7 c__Clostridia8o__Clostridiales9f__Lachnospiraceae:g__[Ruminococcus]; s__gnavus< k__Bacteria= p__Firmicutes> c__Clostridia?o__Clostridiales@f__LachnospiraceaeAg__Bs__C k__BacteriaD p__FirmicutesE c__ClostridiaFo__ClostridialesGf__RuminococcaceaeHg__Is__J k__BacteriaKp__BacteroidetesLc__BacteroidiaMo__BacteroidalesNf__BacteroidaceaeOg__BacteroidesPs__Q k__BacteriaR p__FirmicutesS c__ClostridiaTo__ClostridialesUf__LachnospiraceaeVg__[Ruminococcus]W s__gnavusX k__BacteriaY p__FirmicutesZc__Erysipelotrichi[o__Erysipelotrichales\f__Erysipelotrichaceae]g__^s___ k__Bacteria` p__Firmicutesa c__Clostridiabo__Clostridialescf__Lachnospiraceaedg__[Ruminococcus]e s__gnavusf k__Bacteriagp__Bacteroideteshc__Bacteroidiaio__Bacteroidalesjo__Clostridialeskf__lg__ms__n k__Bacteriao p__Firmicutesp c__Clostridiaqo__Clostridialesrf__sg__ts__u k__Bacteriav p__Firmicutesw c__Clostridiaxo__Clostridialesyf__Lachnospiraceaezg__{s__| k__Bacteria} p__Firmicutes~ c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Actinobacteriac__Coriobacteriiao__Coriobacterialesf__Coriobacteriaceaeg__Adlercreutzias__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Ruminococcuss__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__g__s__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Lachnospiraceaeg__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__Ruminococcaceaeg__Oscillospiras__ k__Bacteria p__Firmicutes c__Clostridias__ k__Bacteriap__Bacteroidetesc__Bacteroidiao__Bacteroidalesf__S24-7g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__o__Clostridialesf__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__g__g__s__ k__Bacteria p__Firmicutes c__Clostridiao__Clostridialesf__ c__Clostridia p__Firmicutes  k__Bacteria 199181 271766 166099 187644233817229459199698SNODJLTREEHEAPXLP@M`OTREEuHEAPX Odataindicesindptr8-- ?@4 4 deflatesm-TA^Pz;|x^]eWow/=A\x "Q[sWYfVeژe r&Éux+[ \<|-//Co$"?< OOBo2|SSЛ Lf7<77E|--kף5M9nxx;;cN^E 0xzC>MD{_$U}?z?g_;a+|ޟ'3Q>oqK8Ny)rsa r|_\BXaW 蕄4 ze/.^ r\JUj^j_=K_ kkW^= CgpD{]$h= _p_p??<Ao0CCD?< 1Mt=D$d?< 鉞Bo6ssћ|BoY{9z+[ *j o&6O&z~l޶D׳|zwBE] %R,]+NvaD癫ѻk=ׁGDދM݌oMt}a݁ޝX]{G?=~zIi3g{/"|/_ޫއ~^~{;?~w~!z1ڋb$^V ;pNr;z>\XO!paW\ \Bt,z^%*W 8\3ӵb޵3qW\konn^V赆 -zk^';SOp/zz?/z ! Eo# (h?< MOOFo SЛ L,f?<y//Doc~X~rg9z+[*j o&ws蝁g9sۆlX_|)zw9 'xz |5:ߍ=1>a/;zߍ=/<܇={G{{'b=ObSϧ,za=σ_@E^ ~W ~&zo6zzE=އ}籏c)z9 +oߣCXO/F|GCw_X1FyQ$Jt=9NmDA//|]_ WbOH,^)J_\rWWD|_`DW5:z5ЫzjkW'Yz>z on^cpSp3V5mmk{p:e?_ 9K9ׇgsxmxS I9{'YYZ?\{q,?p,|?Ҧ2/k/.oګ`h'Iʕryjjb-;qS}^]^=W|6277155777046137u0ww2u6ww5jaimc o`dl fniezFǀǚƙǃ'&&'>ӱ>L{M{sL3<ϴ7|.0//2-6//5-3-426=?k-xxOLyxKyiozNo2o6=g؉^:DvŸr _z)gvs"0_^8 '?K8Z\z8V{\q7| ?"sa0_x:_|{a"0_^//|d0_oګj }/0_d_\~ ߫kz"aq//|{atH9 tH9 O狔|{a0_xN狔|{L|r/| Ea0_xOp8)3|ρ E|r/|/~=|r/|/ |r/|/ޟ)|DS:_ ߛb0_^//gg|sL? |A/?/R^h Ea E|r/|/ a0_|9 6"0_^/+̰,mtm¶M{.4]dڻ Ұ2u9 𕦽]an\e|iZSuöL}7n2 %l;wv;Mwێ1 |iS¶M#_3=?aObSMπ5=g/m/>/_b{x_vߴ}yӴm;aA޻L{?dz>Դsa5޷߇m?M?6b?>1]QyWoM ێ23}^6c_v>{_IH9"_Kxr燯|+_HG1/gbHEo3>"}_Fz-/y[^ *j`=k밾X7OCow|[[gwgA\C|./Kws9 ox'xz |5zw-:= w+oG{|zAC= #G{,8OD)?޳=~z/e^Uk}u [ww{/>?D#?ާ}/_ zF]z?FG;D/7|.&x^j@dj۶m۶mm۶m۶mߤs?k{W{y3IΙՆpɃ_p߷{[8-߷``} ipP=}v_cW7ѽ??m{nG5hS3ѽy~nJ5 ;E>wt %z=wcWxr >`c{EOO; >>+wNz 1O#pc |5k[p;cur pW~7sp<`3χ^aGoz#hƢ?1&1GT47g&x|1G_?sћ|%-9??[VaAo-z1F77mmox'xzދ>w#8z'?>GGл U5|[E}~{#c'=E9|/ߏ^a轁-zg89O/=%s=xzr>6>/wz| ^HCCpE/"7G? 'WWL~_l_^}&@?W_|lΟ$%E/89|)K NN^ӂӡ Le? 8+zΉ^??zsGxtoG3 <QFo cD$&?<iMDosЛ9-oG_c~,x9z+0J*Vc5Co=|0F߄6ÿmgG?w`}܅ٍ0zG? >qNOOw3|=r]2 zWѻu n |;G|=zx=zxS3c轂5 -zG><y <DZ,ݳ-=P腆? 8,z,^$#hń?86zq ^Ϥ%C/9))K_jppZeggA/+,|na{NO.r|G+ _K_b1K/JWRx}iKg/kˡ_ U2byWZF׿.zЫZF1zMk^3[@V5zmk^;p{:Nu+zOp/zz}706fi7Bo4 x,xz$&7 ̈́ox6xzsK_Z-o2_^J*Vc5o=xx#z ނVK6Kvv]o/xx?z;=G? >qS}4gg;g;E./wkݰ&ѻ]K=o|C#c'=|~+^c7=z1'g`~C;z? obr[ uϲ|D/zma0_XE?28 zQ^ bbXF/qЋ^[>K pRSS Զ`Z̓O^2bLe/+88;z9 ΅^n[<C/?+^a[?Wb@$|e+ 9y[~[ֿVTB2zUЫ _ujkkW:m}F5?7-kyZ[󵅿=z:ago nu<==_o?>7r?[x@7??Boo x,zl~o7Mo2曂Sm}tf7Fos[??,%8oz1 JVW׀ע mzne3z[ 6vv =Co?|; Qz=|Swz?.w 2_*ѻa mw={ SA-G'g =Y9TREE}-SNODOvw--  deflate-TA^Xnn  deflateӍnTA^Xmm deflatemTA^PTREESHEAPX8[|metadatagroup-metadatamatrixids 3|HEAPX|P3Sx^VKQ]VQ5RAۆx``Q-̕mMQQ8N J0FLVN9@EZ ADCnjx߹= {ǹ{6ܑL_OLac]Tv$u$&]Hky ăd:;{Υs~T$N)>K{G~/tWmS<9xGb--~mͼ~c}<:sy?r|#A =z<%8-)'.;ơy[{Ip0oZW kyB`ҁp37Q/{<쁓~dx1ō:qx^C8O@Tٞ<5㾠kA_1dN~s1i+7PXG8#iַ?{T>Ӏz5UoJ/y89b}'^:'+aTΖ'X'`=^IZ?.6ܳ,p3 '9~ἧ\x̓yظŽ~2^Յiikq}g\7?@\ |g| ~/QSq/8^@ݺynaY};1Y0g|"/>">p}<}wXS'1_p<7w8x1V22_yyyoSױvw}qo |Vdž^?{=/ }x^m j1 DΞ8I?(ݲJ3u]o_qcz֟wgK߷{._v>jN37b;Y!.;'F?٪OKN>7: ۯIygsXlRwf.]-'io;7}"f}'0._j[j˃Ͼs"8fG ѳ<8Lnjv-0ӉowݗE87w9]O͘mg'7/s=CYOz3ٖ/; |jM|mF8˶n;C< ; X_r;bhr`S[]~7/_1|^Sc7!N<})>jx^svjj61LOL9y&N8q><ANp3u=޿@ %HC:@F2B(ad%;DHE~ P"%┥H%PԢ.OCbhD҂6H':Ӆt;q}G0A e#(F3c<d2d\1,b1KYN<+X*Vc=6"}9ap9YΑy.q\#6w='<9/x+^󆷼go|'RiIO0H&B(ad%D\&|;?/~O4)IEIKz2B( 'M$yK>(@Q)FqJPcx^c``` > , @\ {210NE4TREE-TREE67nTREEE3mGCOL@259434260756260205260753336145275819261334194787 178735 170950 260058 179063 266595312476179069196138275869194978276044439687782940117971927626027556333928422592122709842624094484382185743302407 258969!184151"215193#4468234$97294%259266&259263'184567(181141)227886*174791+269019,260653-166911.260655/4439450732128131856322724543192971419538552611776229386720414482670419301870:267123;239571<349142=199403>208571?334365@260397A403497B267388C274106D274844E288931F275136G267411H4338733I4403349J314810K3621189L182033M1105328N180105O195005P275627Q187233R343581S291750T301012U269902V276985W274438X259228Y303479Z174754[263908\380534]4397402^335952_1108453`258725a351881b320490c264496d194662e191772f187790g174056h180972i340189j183390k422727l263546m187989n259910o303652p261419q4449524r115186s322062t269576u260663v135956w267689x162991y274021z4414420{1571092|234121}267457~26745219953417557325959319346327617219731818141944149425985927570718707827051944711352639462764042935801878072633621832114346374259175181603270385188536440770319139817611816707883339017380726681616990118299526316544424593104901684221697874321484212077526892317543217033526038719721618652119046044185861865264374042436341176039177427191483343906351794553395178779268755271378231169259335110794545004726935943317601820162754702703911877031918163537821638623199093075952751502729532699921768861954453460982750788284351918873330531672042590562333131643082637051786591742723107481321143362145508072628691780311734173168423763971902421826211749592593721754168472284615243503812590123272363183702651062144714372578412746027459731496326216644175391003441705552615112735151772058392154462541261409274521180919191958216403264373185754262766176850176858268121 4364243 272812 185222 262399 178926199307265641453633518591791811791882658282927452693788374731303352598881967774528233434202575787210950194822 266483!260828"348398#3206355$231021%262677&259249'197775(181205)196194*263452+270396,263106-4480176.344527/27533906871851181091218713332704914213700152684166177802719027381910779331965:180362;258522<169398=273084>3265889?270662@271449A4365109B276663C186497D264021E264787F2112006G263044H3117556I233411J196825K337331L354957M274018N183106O261606P195840Q265940R181249S214919T265786U174663V185777W172705X274422Y259609Z311174[268581\1136443]4329571^330296_181344`276531a180206b258250c827195d206494e276580f197790g 10084.PC.636h 10084.PC.635i 10084.PC.607j 10084.PC.634k 10084.PC.355l 10084.PC.354m 10084.PC.356n 10084.PC.593o 10084.PC.481@x^mewl0I6@ER PP[wfgo~sBDH>dy:')x*}ȓqx<'x!x(x$G15nv}"~~q'ww]q7ܝz{{{>/x . B0.}ȝLzQ\ %pI\ epY\s "+**WW5pM\ up]\O׀7qgqfn[߃!mx;~?ŏI~: Y럧_/x'~_ů]x7~oC?~>?_/A|_|q>߇  J !> ?_8Ήs8yߙT'qj>I95ORN')'t;$GĹprN{|8?. B0.3qQ\ W^¥qW^"+*j`> |[5pM\ up]\ pC7MpSn[ָ n;θ S/ܛ}}@<CP< #H< cX<.?>>> OST< O34|:>gsL|n,y||_/ƗKC|=YW5Z|yx>^C|7o7Ex1^| |3%J ī|'^kB|}kFMo›>|?~ Bߚx^KSa/+Q5TE6 хye7MQQ[SO -Y-*fd~ E2&"ySse{JkY$*KrY6EMZLt 5i1bE uly_X9>f邏?]rt?o1H6x3mO|hi9H~g-=%!+oiJ?_~;hxﱯP]`1)ۃqsgnDFOLƦ-?ỉw'cƂ4҇Q7zL w+3ߟF{r/~&}sVH~71:Eѯ惌[O-thW>=3rs,S[wH$h0s#_ 4YH_UogvJs~%1qv]ϗ%˧ϖ}!4e_K u=^ǵ?z`;'þlz?9y-sz?麰A8D;x`pN,~{}zǿM; ( } R:>GN9/vڻi\swWHk/}x^a``0@Egsh 4~&? HS, TREESNODS}0({}3S|3|TREEHEAPXPTREE HEAPX sdataindicesindptr8-- ?@4 4 deflate-TA^PTREEx-SNODS --  deflatec-TA^XTREE-x^eee 1Cwwwww7؝ݭ`+` &vw5v(a󭗵q9<$BICyTBMBmC4,2Cq*q62\57v܍f<&(' _'l? J>(b(C>Zz/acc-y>Qh>AX}qĹrp-n:܌;^Ż?w9GOP 1:9ǒ{$]eQ}Ǟ -=C0#1Kq1rYNYxiH7oyeQQՒX7eh腁Q ss)."=r3No`>șcɽ?49r߆p<徿WP}+ wTz<1݈9 Ÿܑw$b9V&Ϡ(JN4BSy]{?LdL{qHEAPX8h;metadatagroup-metadatamatrixids ;>TREEHEAPX(>PSNOD??B0Q(BBD;;>?BTREEHEAPX0BPBDTREEXFHEAPX Ddataindicesindptr8 ?@4 4 deflateҽ^PSNODHEGH  deflateҽ^X  deflateIҽ^XTREE / ?@4 4 deflateҽ^Pq2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/extra_tip.tree000066400000000000000000000000551462552567500253000ustar00rootroot00000000000000((O1:0.25,O2:0.5):0.25,O3:0.75,O4:1.25)root; q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/faith_test.tree000066400000000000000000000000401462552567500254250ustar00rootroot00000000000000((A:0.3,B:0.50):0.2,C:100)root; q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/faith_test_table.biom000066400000000000000000001020401462552567500265660ustar00rootroot00000000000000HDF   `  TREExHEAPX observationsample8 @id  @type H format-url P format-version@ H generated-by Hcreation-date H shape@ 0 nnz@ TREEGCOL No Table IDhttp://biom-format.orgtesting2020-02-10T10:40:00.969281CBA S4 S5 S3 S2 S1HEAPX8 metadatagroup-metadatamatrixids SNODQQSTREEHEAPX(PSNOD 0PH(  " TREEHEAPX0 P "TREE,HEAPX "dataindicesindptr8  ?@4 4 deflateX$ pA^PTREE - SNODH#5?x^c``wa0{D x^c````bf fbF(Ćx^c````bv bx^cdh|6 1x^c`` N? `ҢBcC8 x^c````d̈́x^c````b& fb6 bx^cb LH|4>74j   deflate6 pA^XTREE-   deflate @pA^XTREE . deflate`IpA^PTREE.QSTREEWHEAPX8Smetadatagroup-metadatamatrixids xTVTREEHEAPXVPSNODXXXZ0z([@[`]PTxTVXZTREEHEAPXZP@[`]TREEgHEAPX ]dataindicesindptr8  ?@4 4 deflate^ pA^PTREE5. SNOD]`hq   deflatepi pA^XTREES.   deflaterpA^XTREEh. deflate{pA^PTREE .q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/faith_test_table_pa.biom000066400000000000000000001020401462552567500272460ustar00rootroot00000000000000HDF   `  TREExHEAPX observationsample8 @id  @type H format-url P format-version@ H generated-by Hcreation-date H shape@ 0 nnz@ TREEGCOL No Table IDhttp://biom-format.orgtesting2020-02-10T09:35:55.141392CBA S4 S5 S3 S2 S1HEAPX8 metadatagroup-metadatamatrixids SNODQQSTREEHEAPX(PSNOD 0PH(  " TREEHEAPX0 P "TREE,HEAPX "dataindicesindptr8  ?@4 4 deflateX$ kA^PTREE- SNODH#5?x^c``hN x^c````bf fbF(Ćx^c````bv bx^cdh|6 1x^c``hN x^c````d̈́x^c````b& fb6 bx^cb LH|4>74j   deflate6 kA^XTREE-   deflate @kA^XTREE- deflate`IkA^PTREE.QSTREEWHEAPX8Smetadatagroup-metadatamatrixids xTVTREEHEAPXVPSNODXXXZ0z([@[`]PTxTVXZTREEHEAPXZP@[`]TREEgHEAPX ]dataindicesindptr8  ?@4 4 deflate^ kA^PTREE&. SNOD]`hq   deflatepi kA^XTREE7.   deflaterkA^XTREEL. deflate{kA^PTREE d.q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/faith_test_table_rf.biom000066400000000000000000001020401462552567500272550ustar00rootroot00000000000000HDF   `  TREExHEAPX observationsample8 @id  @type H format-url P format-version@ H generated-by Hcreation-date H shape@ 0 nnz@ TREEGCOL No Table IDhttp://biom-format.orgtesting2020-02-10T09:13:48.061465CBA S4 S5 S3 S2 S1HEAPX8 metadatagroup-metadatamatrixids SNODQQSTREEHEAPX(PSNOD 0PH(  " TREEHEAPX0 P "TREE,HEAPX "dataindicesindptr8  ?@4 4 deflateX$ 74j   deflate6 :  deflate6[<^XTREE-  deflate @[<^XTREE- deflate`I[<^PTREE.QSTREEWHEAPX8Smetadatagroup-metadatamatrixids xTVTREEHEAPXVPSNODXXXZ0z([@[`]PTxTVXZTREEHEAPXZP@[`]TREEgHEAPX ]dataindicesindptr8 ?@4 4 deflate^[<^PTREE.SNOD]`hq  deflatepi[<^XTREE-.  deflater[<^XTREE;. deflate{[<^PTREEN.q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/two_feature_rf_table.biom000066400000000000000000001020401462552567500274470ustar00rootroot00000000000000HDF   `  TREExHEAPX observationsample8 @id  @type H format-url P format-version@ H generated-by Hcreation-date H shape@ 0 nnz@TREEGCOL No Table IDhttp://biom-format.orggenerated in JN2020-02-10T09:24:53.089738O2O1S3 S2 S1HEAPX8 metadatagroup-metadatamatrixids SNODQQSTREEHEAPX(PSNOD 0PH(  " TREEHEAPX0 P "TREE,HEAPX "dataindicesindptr8 ?@4 4 deflateX$ՃA^PTREE-SNODH#5?x^c``eϚ 77 qx^cd```bF(Xx^c````bV 0x^cbLH|6 T"x^c``6fM{ qx^cdF$8x^c````bf fbH x^cbLH|N4>:  deflate6ՃA^XTREE-  deflate @ՃA^XTREE. deflate`IՃA^PTREE.QSTREEWHEAPX8Smetadatagroup-metadatamatrixids xTVTREEHEAPXVPSNODXXXZ0z([@[`]PTxTVXZTREEHEAPXZP@[`]TREEgHEAPX ]dataindicesindptr8 ?@4 4 deflate^ՃA^PTREE*.SNOD]`hq  deflatepiՃA^XTREEG.  deflaterՃA^XTREEU. deflate{ՃA^PTREEh.q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/data/two_feature_table.biom000066400000000000000000001020601462552567500267620ustar00rootroot00000000000000HDF  0`  TREEHEAPX observationsample8 @id ` @type` H format-url` P format-version@ H generated-by ` Hcreation-date` H shape@ 0 nnz@`TREE @ GCOL No Table IDhttp://biom-format.org test_code2018-10-31T16:11:01.097745O2O1S3 S2 S1HEAPX8metadatagroup-metadatamatrixids SNOD`QQSTREEHEAPX`PSNOD( 0`H(  " @TREEHEAPX@ P "TREE,HEAPX #dataindicesindptr8 ?@4 4 deflateh$6[PTREE-SNODX#5 ?x^c``B0Px^cd```bF(Xx^c````bV 0x^cb``H`v fB1Tx^c`` p ^x^cdF$8x^c````bf fbH x^cb``H`. fBsA%4Z  deflate66[XTREE-  deflate0@6[XTREE . deflatepI6[PTREE.QSTREE WHEAPX8Tmetadatagroup-metadatamatrixids TVTREE WHEAPXVPSNODhXXZ0z(([P[p]`TTVXZTREE WHEAPXZPP[p]TREE(gHEAPX ]dataindicesindptr8 ?@4 4 deflate^6[PTREE4.SNOD]phq  deflatei6[XTREEJ.  deflater6[XTREEX. deflate|6[PTREEk.q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/test_alpha.py000066400000000000000000000331371462552567500242140ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from subprocess import CalledProcessError import numpy as np import numpy.testing as npt import pandas as pd import pandas.testing as pdt import biom from qiime2.plugin.testing import TestPluginBase from qiime2 import Artifact from ..alpha import (pielou_evenness, observed_features, shannon_entropy, METRICS, _berger_parker, _brillouin_d, _simpsons_dominance, _esty_ci, _goods_coverage, _margalef, _mcintosh_d, _strong ) class SmokeTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() empty_table = biom.Table(np.array([]), [], []) self.empty_table = Artifact.import_data('FeatureTable[Frequency]', empty_table) def test_non_phylogenetic_passed_empty_table(self): for metric in METRICS['NONPHYLO']['IMPL']: metric = METRICS['NAME_TRANSLATIONS'][metric] with self.assertRaisesRegex(ValueError, 'empty'): self.plugin.actions[metric](table=self.empty_table) class FaithPDTests(TestPluginBase): package = 'q2_diversity_lib.tests' def artifact(self, semantic_type, fp): return Artifact.import_data(semantic_type, self.get_data_path(fp)) @staticmethod def assert_vec_equal(actual_art, expected): actual = actual_art.view(pd.Series) pdt.assert_series_equal(actual, expected) def setUp(self): super().setUp() self.fn = self.plugin.actions['faith_pd'] self.tbl = self.artifact('FeatureTable[Frequency]', 'faith_test_table.biom') self.tre = self.artifact('Phylogeny[Rooted]', 'faith_test.tree') self.expected = pd.Series({'S1': 0.5, 'S2': 0.7, 'S3': 1.0, 'S4': 100.5, 'S5': 101}, name='faith_pd') def test_receives_empty_table(self): table = self.artifact('FeatureTable[Frequency]', 'empty_table.biom') with self.assertRaisesRegex(ValueError, 'empty'): self.fn(table=table, phylogeny=self.tre) def test_method(self): actual_art, = self.fn(table=self.tbl, phylogeny=self.tre) self.assert_vec_equal(actual_art, self.expected) def test_accepted_types_have_consistent_behavior(self): rf_tbl = self.artifact('FeatureTable[RelativeFrequency]', 'faith_test_table_rf.biom') pa_tbl = self.artifact('FeatureTable[PresenceAbsence]', 'faith_test_table_pa.biom') for table in [self.tbl, rf_tbl, pa_tbl]: actual_art, = self.fn(table=table, phylogeny=self.tre) self.assert_vec_equal(actual_art, self.expected) def test_passed_tree_missing_tip(self): tree = self.artifact('Phylogeny[Rooted]', 'missing_tip.tree') with self.assertRaises(CalledProcessError): obs = self.fn(table=self.tbl, phylogeny=tree) self.assertTrue('not a subset of the tree tips' in obs.stderr) def test_passed_rootonlytree(self): tree = self.artifact('Phylogeny[Rooted]', 'root_only.tree') with self.assertRaises(CalledProcessError): obs = self.fn(table=self.tbl, phylogeny=tree) self.assertTrue('not a subset of the tree tips' in obs.stderr) class ObservedFeaturesTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.input_table = biom.Table(np.array([[1, 0, .5, 999, 1], [0, 1, 2, 0, 5], [0, 0, 0, 1, 10]]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4', 'S5']) # Calculated by hand: self.expected = pd.Series( {'S1': 1, 'S2': 1, 'S3': 2, 'S4': 2, 'S5': 3}, name='observed_features') def test_method(self): actual = observed_features(table=self.input_table) pdt.assert_series_equal(actual, self.expected) def test_accepted_types_have_consistent_behavior(self): freq_table = self.input_table rel_freq_table = self.input_table.norm(axis='sample', inplace=False) p_a_table = self.input_table.pa(inplace=False) accepted_tables = [freq_table, rel_freq_table, p_a_table] for table in accepted_tables: actual = observed_features(table) pdt.assert_series_equal(actual, self.expected) class PielouEvennessTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.input_table = biom.Table(np.array([[0, 1, 1, 1, 999, 1], [0, 0, 1, 1, 999, 1], [0, 0, 0, 1, 999, 2]]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4', 'S5', 'S6']) # Calculated by hand: self.expected = pd.Series( {'S1': np.NaN, 'S2': np.NaN, 'S3': 1, 'S4': 1, 'S5': 1, 'S6': 0.946394630357186}, name='pielou_evenness') def test_method(self): actual = pielou_evenness(table=self.input_table) pdt.assert_series_equal(actual, self.expected) def test_accepted_types_have_consistent_behavior(self): freq_table = self.input_table rel_freq_table = self.input_table.norm(axis='sample', inplace=False) accepted_tables = [freq_table, rel_freq_table] for table in accepted_tables: actual = pielou_evenness(table) pdt.assert_series_equal(actual, self.expected) def test_drop_undefined_samples(self): NaN_table = biom.Table(np.array([[0, 1, 0, 0, 1, 1], [0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 0, 1]]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4', 'S5', 'S6']) # pandas supports floating point correction for float dtype only, # these 1 ints were changed to 1.0 floats to get that support expected = pd.Series({'S5': 1.0, 'S6': 1.0}, name='pielou_evenness') actual = pielou_evenness(table=NaN_table, drop_undefined_samples=True) pdt.assert_series_equal(actual, expected, check_dtype=False) def test_do_not_drop_undefined_samples(self): NaN_table = biom.Table(np.array([[0, 1, 0, 0, 1, 1], [0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 0, 1]]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4', 'S5', 'S6']) expected = pd.Series({'S1': np.NaN, 'S2': np.NaN, 'S3': np.NaN, 'S4': np.NaN, 'S5': 1, 'S6': 1}, name='pielou_evenness') actual = pielou_evenness(table=NaN_table, drop_undefined_samples=False) pdt.assert_series_equal(actual, expected) class ShannonEntropyTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.input_table = biom.Table(np.array([[0, 0, 0, 0, 1, 1], [0, 1, 0, 0, 1, 1], [0, 0, 1, 0, 1, 1], [0, 0, 1, 0, 0, 1]]), ['A', 'B', 'C', 'D'], ['S1', 'S2', 'S3', 'S4', 'S5', 'S6']) # Calculated by hand: self.expected = pd.Series( {'S1': np.NaN, 'S2': 0, 'S3': 1, 'S4': np.NaN, 'S5': 1.584962500721156, 'S6': 2}, name='shannon_entropy') def test_method(self): actual = shannon_entropy(table=self.input_table) pdt.assert_series_equal(actual, self.expected) def test_accepted_types_have_consistent_behavior(self): freq_table = self.input_table rel_freq_table = self.input_table.norm(axis='sample', inplace=False) accepted_tables = [freq_table, rel_freq_table] for table in accepted_tables: actual = shannon_entropy(table) pdt.assert_series_equal(actual, self.expected) def test_drop_undefined_samples(self): expected = pd.Series({'S2': 0, 'S3': 1, 'S5': 1.584962500721156, 'S6': 2}, name='shannon_entropy') actual = shannon_entropy(table=self.input_table, drop_undefined_samples=True) pdt.assert_series_equal(actual, expected, check_dtype=False) def test_do_not_drop_undefined_samples(self): actual = shannon_entropy(table=self.input_table, drop_undefined_samples=False) pdt.assert_series_equal(actual, self.expected) class AlphaPassthroughTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.method = self.plugin.actions['alpha_passthrough'] empty_table = biom.Table(np.array([]), [], []) self.empty_table = Artifact.import_data('FeatureTable[Frequency]', empty_table) crawford_tbl = self.get_data_path('crawford.biom') self.crawford_tbl = Artifact.import_data('FeatureTable[Frequency]', crawford_tbl) def test_method(self): for metric in METRICS['NONPHYLO']['UNIMPL']: # NOTE: crawford table used b/c input_table too minimal for `ace` self.method(table=self.crawford_tbl, metric=metric) # If we get here, then our methods ran without error self.assertTrue(True) def test_passed_empty_table(self): for metric in METRICS['NONPHYLO']['UNIMPL']: with self.assertRaisesRegex(ValueError, 'empty'): self.method(table=self.empty_table, metric=metric) def test_passed_bad_metric(self): with self.assertRaisesRegex(TypeError, 'imaginary_metric.*incompatible'): self.method(table=self.crawford_tbl, metric='imaginary_metric') def test_passed_implemented_metric(self): # alpha_passthrough does not provide access to measures that have been # implemented locally for metric in METRICS['NONPHYLO']['IMPL']: with self.assertRaisesRegex(TypeError, f"{metric}.*incompatible"): self.method(table=self.crawford_tbl, metric=metric) # tests for passthrough metrics were sourced from skbio def test_berger_parker_d(self): self.assertEqual(_berger_parker(np.array([5, 5])), 0.5) self.assertEqual(_berger_parker(np.array([1, 1, 1, 1, 0])), 0.25) def test_brillouin_d(self): self.assertAlmostEqual(_brillouin_d(np.array([1, 2, 0, 0, 3, 1])), 0.86289353018248782) def test_esty_ci(self): def _diversity(indices, f): """Calculate diversity index for each window of size 1. indices: vector of indices of taxa f: f(counts) -> diversity measure """ result = [] max_size = max(indices) + 1 freqs = np.zeros(max_size, dtype=int) for i in range(len(indices)): freqs += np.bincount(indices[i:i + 1], minlength=max_size) try: curr = f(freqs) except (ZeroDivisionError, FloatingPointError): curr = 0 result.append(curr) return np.array(result) data = [1, 1, 2, 1, 1, 3, 2, 1, 3, 4] observed_lower, observed_upper = zip(*_diversity(data, _esty_ci)) expected_lower = np.array([1, -1.38590382, -0.73353593, -0.17434465, -0.15060902, -0.04386191, -0.33042054, -0.29041008, -0.43554755, -0.33385652]) expected_upper = np.array([1, 1.38590382, 1.40020259, 0.67434465, 0.55060902, 0.71052858, 0.61613483, 0.54041008, 0.43554755, 0.53385652]) npt.assert_array_almost_equal(observed_lower, expected_lower) npt.assert_array_almost_equal(observed_upper, expected_upper) def test_simpson(self): self.assertAlmostEqual(_simpsons_dominance(np.array([1, 0, 2, 5, 2])), 0.66) self.assertAlmostEqual(_simpsons_dominance(np.array([5])), 0) def test_goods_coverage(self): counts = [1] * 75 + [2, 2, 2, 2, 2, 2, 3, 4, 4] obs = _goods_coverage(counts) self.assertAlmostEqual(obs, 0.23469387755) def test_margalef(self): self.assertEqual(_margalef(np.array([0, 1, 1, 4, 2, 5, 2, 4, 1, 2])), 8 / np.log(22)) def test_mcintosh_d(self): self.assertAlmostEqual(_mcintosh_d(np.array([1, 2, 3])), 0.636061424871458) def test_strong(self): self.assertAlmostEqual(_strong(np.array([1, 2, 3, 1])), 0.214285714) q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/test_beta.py000066400000000000000000000523641462552567500240450ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- import os from subprocess import CalledProcessError import numpy as np import numpy.testing as npt import biom import skbio import pkg_resources from qiime2.plugin.testing import TestPluginBase from qiime2 import Artifact from ..beta import bray_curtis, jaccard, METRICS # ----------------------------Non-Phylogenetic--------------------------------- class BrayCurtisTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.input_table = biom.Table(np.array([[1, 0, 999, 1], [0, .5, 0, 1], [0, 0, 1, 1]]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4']) self.expected = skbio.DistanceMatrix( [[0.0000000, 1, 0.998001998, 0.5], [1, 0.0000000, 1, .714285714], [0.998001998, 1, 0.0000000, .996011964], [0.5, .714285714, .996011964, 0.0000000]], ids=['S1', 'S2', 'S3', 'S4']) def test_method(self): actual = bray_curtis(table=self.input_table, n_jobs=1) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_accepted_types_have_consistent_behavior_rarefied(self): rarefied_table = biom.Table(np.array([[3, 1, 2, 6], [1, 3, 2, 0], [2, 2, 2, 0]]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4']) normalized_rarefied_table = rarefied_table.norm(axis='sample', inplace=False) self.expected = skbio.DistanceMatrix( [[0.0000000, 0.333333333333333, 0.1666666667, 0.5], [0.333333333333333, 0.0000000, 0.1666666667, 0.833333333333], [0.1666666667, 0.1666666667, 0.0000000, 0.66666666667], [0.5, 0.833333333333, 0.66666666667, 0.0000000]], ids=['S1', 'S2', 'S3', 'S4']) accepted_tables = [rarefied_table, normalized_rarefied_table] for table in accepted_tables: actual = bray_curtis(table=table) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_accepted_types_have_inconsistent_behavior_unrarefied(self): freq_table = self.input_table relative_freq_table = self.input_table.norm(axis='sample', inplace=False) # count data should pass, as in test_method actual = bray_curtis(table=freq_table) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) # normalized data should fail actual = bray_curtis(table=relative_freq_table) with self.assertRaisesRegex(AssertionError, "not almost equal"): for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_bray_curtis_relative_frequency(self): input_table = biom.Table( np.array([ [0.3, 0, 0.77, 0.5], [0.1, 0, 0.15, 0.25], [0.6, 1, 0.08, 0.25] ]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4'] ) expected = skbio.DistanceMatrix( [ [0.0000000, 0.4, 0.52, 0.35], [0.4, 0.0000000, 0.92, 0.75], [0.52, 0.92, 0.0000000, 0.27], [0.35, 0.75, 0.27, 0.0000000] ], ids=['S1', 'S2', 'S3', 'S4'] ) actual = bray_curtis(table=input_table, n_jobs=1) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal( actual[id1, id2], expected[id1, id2] ) class JaccardTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.freq_table = biom.Table(np.array([[1, 0, 999, 1], [0, .5, 0, 1], [0, 0, 1, 1]]), ['A', 'B', 'C'], ['S1', 'S2', 'S3', 'S4']) self.expected = skbio.DistanceMatrix( [[0.0000000, 1, 0.5, 0.6666667], [1, 0.0000000, 1, 0.6666667], [0.5, 1, 0.0000000, 0.3333333], [0.6666667, 0.6666667, 0.3333333, 0.0000000]], ids=['S1', 'S2', 'S3', 'S4']) self.relative_freq_table = self.freq_table.norm(axis='sample', inplace=False) self.p_a_table = self.freq_table.pa(inplace=False) def test_method(self): actual = jaccard(table=self.freq_table, n_jobs=1) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_accepted_types_have_consistent_behavior(self): accepted_tables = [self.freq_table, self.relative_freq_table, self.p_a_table] for table in accepted_tables: actual = jaccard(table=table) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) # ---------------------------------Phylogenetic------------------------------- class UnweightedUnifrac(TestPluginBase): package = 'q2_diversity_lib.tests' def artifact(self, semantic_type, fp): return Artifact.import_data(semantic_type, self.get_data_path(fp)) def setUp(self): super().setUp() self.fn = self.plugin.actions['unweighted_unifrac'] # expected computed with skbio.diversity.beta_diversity self.expected = skbio.DistanceMatrix([[0.00, 0.25, 0.25], [0.25, 0.00, 0.00], [0.25, 0.00, 0.00]], ids=['S1', 'S2', 'S3']) self.tbl = self.artifact('FeatureTable[Frequency]', 'two_feature_table.biom') self.tre = self.artifact('Phylogeny[Rooted]', 'three_feature.tree') def test_method(self): actual_art, = self.fn(self.tbl, self.tre) actual = actual_art.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_method_bypass_tips(self): actual_art, = self.fn(self.tbl, self.tre, bypass_tips=True) actual = actual_art.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, self.expected.ids) def test_accepted_types_have_consistent_behavior(self): rf_tbl = self.artifact('FeatureTable[RelativeFrequency]', 'two_feature_rf_table.biom') pa_tbl = self.artifact('FeatureTable[PresenceAbsence]', 'two_feature_p_a_table.biom') for table in [self.tbl, rf_tbl, pa_tbl]: actual_art, = self.fn(table=table, phylogeny=self.tre) actual = actual_art.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_missing_tips_tree(self): tre = self.artifact('Phylogeny[Rooted]', 'root_only.tree') with self.assertRaises(CalledProcessError): obs = self.fn(self.tbl, tre) self.assertTrue('not a subset of the tree tips', obs.stderr) def test_extra_tip_tree(self): tbl = self.artifact('FeatureTable[Frequency]', 'two_feature_table.biom') tre_2 = self.artifact('Phylogeny[Rooted]', 'two_feature.tree') obs_2_art, = self.fn(tbl, tre_2) obs_2 = obs_2_art.view(skbio.DistanceMatrix) tre_3 = self.artifact('Phylogeny[Rooted]', 'three_feature.tree') obs_3_art, = self.fn(tbl, tre_3) obs_3 = obs_3_art.view(skbio.DistanceMatrix) self.assertEqual(obs_2, obs_3) class WeightedUnifrac(TestPluginBase): package = 'q2_diversity_lib.tests' def artifact(self, semantic_type, fp): return Artifact.import_data(semantic_type, self.get_data_path(fp)) def setUp(self): super().setUp() self.fn = self.plugin.actions['weighted_unifrac'] # expected computed with diversity.beta_phylogenetic (weighted_unifrac) self.expected = skbio.DistanceMatrix( np.array([0.44656238, 0.23771096, 0.30489123, 0.23446002, 0.65723575, 0.44911772, 0.381904, 0.69144829, 0.39611776, 0.36568012, 0.53377975, 0.48908025, 0.35155196, 0.28318669, 0.57376916, 0.23395746, 0.24658122, 0.60271637, 0.39802552, 0.36567394, 0.68062701, 0.36862049, 0.48350632, 0.33024631, 0.33266697, 0.53464744, 0.74605075, 0.53951035, 0.49680733, 0.79178838, 0.37109012, 0.52629343, 0.22118218, 0.32400805, 0.43189708, 0.59705893]), ids=('10084.PC.481', '10084.PC.593', '10084.PC.356', '10084.PC.355', '10084.PC.354', '10084.PC.636', '10084.PC.635', '10084.PC.607', '10084.PC.634')) self.tbl = self.artifact('FeatureTable[Frequency]', 'crawford.biom') self.tre = self.artifact('Phylogeny[Rooted]', 'crawford.nwk') def test_method(self): actual_art, = self.fn(self.tbl, self.tre) actual = actual_art.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_method_bypass_tips(self): actual_art, = self.fn(self.tbl, self.tre, bypass_tips=True) actual = actual_art.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, self.expected.ids) def test_accepted_types_have_consistent_behavior(self): tbl_rf = self.artifact('FeatureTable[RelativeFrequency]', 'crawford_rf.biom') for table in [self.tbl, tbl_rf]: actual_art, = self.fn(table=table, phylogeny=self.tre) actual = actual_art.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, self.expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], self.expected[id1, id2]) def test_missing_tips_tree(self): tre = self.artifact('Phylogeny[Rooted]', 'root_only.tree') with self.assertRaises(CalledProcessError): obs = self.fn(self.tbl, tre) self.assertTrue('not a subset of the tree tips', obs.stderr) def test_extra_tip_tree(self): tbl = self.artifact('FeatureTable[Frequency]', 'two_feature_table.biom') tre_2 = self.artifact('Phylogeny[Rooted]', 'two_feature.tree') obs_2_art, = self.fn(tbl, tre_2) obs_2 = obs_2_art.view(skbio.DistanceMatrix) tre_3 = self.artifact('Phylogeny[Rooted]', 'three_feature.tree') obs_3_art, = self.fn(tbl, tre_3) obs_3 = obs_3_art.view(skbio.DistanceMatrix) self.assertEqual(obs_2, obs_3) class BetaPhylogeneticMetaPassthroughTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.fn = self.plugin.actions['beta_phylogenetic_meta_passthrough'] self.empty_tbl = Artifact.import_data( 'FeatureTable[Frequency]', biom.Table(np.array([]), [], [])) # checking parity with the unifrac.meta tests def unifrac_data(fn): path = os.path.join('data', fn) return pkg_resources.resource_filename('unifrac.tests', path) self.tables = [ Artifact.import_data('FeatureTable[Frequency]', unifrac_data('e1.biom')), Artifact.import_data('FeatureTable[Frequency]', unifrac_data('e2.biom')), ] self.trees = [ Artifact.import_data('Phylogeny[Rooted]', unifrac_data('t1.newick')), Artifact.import_data('Phylogeny[Rooted]', unifrac_data('t2.newick')), ] def test_method(self): for metric in METRICS['PHYLO']['UNIMPL']: obs_art, = self.fn(tables=self.tables, phylogenies=self.trees, metric=metric) obs = obs_art.view(skbio.DistanceMatrix) self.assertEqual(('A', 'B', 'C'), obs.ids) self.assertEqual((3, 3), obs.shape) def test_passed_bad_metric(self): with self.assertRaisesRegex(TypeError, 'imaginary_metric.*incompatible'): self.fn(tables=self.tables, phylogenies=self.trees, metric='imaginary_metric') class BetaPassthroughTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.method = self.plugin.actions['beta_passthrough'] empty_table = biom.Table(np.array([]), [], []) self.empty_table = Artifact.import_data('FeatureTable[Frequency]', empty_table) crawford_tbl = self.get_data_path('crawford.biom') self.crawford_tbl = Artifact.import_data('FeatureTable[Frequency]', crawford_tbl) table = biom.Table(np.array([[0, 1, 3], [1, 1, 2]]), ['O1', 'O2'], ['S1', 'S2', 'S3']) self.table = Artifact.import_data('FeatureTable[Frequency]', table) def test_method(self): for metric in METRICS['NONPHYLO']['UNIMPL']: self.method(table=self.crawford_tbl, metric=metric) # If we get here, then our methods ran without error self.assertTrue(True) def test_passed_empty_table(self): for metric in METRICS['NONPHYLO']['UNIMPL']: with self.assertRaisesRegex(ValueError, 'empty'): self.method(table=self.empty_table, metric=metric) def test_passed_bad_metric(self): with self.assertRaisesRegex(TypeError, 'imaginary_metric.*incompatible'): self.method(table=self.crawford_tbl, metric='imaginary_metric') def test_passed_implemented_metric(self): # beta_passthrough does not provide access to measures that have been # implemented locally for metric in METRICS['NONPHYLO']['IMPL']: with self.assertRaisesRegex(TypeError, f"{metric}.*incompatible"): self.method(table=self.crawford_tbl, metric=metric) def test_aitchison(self): actual, = self.method(table=self.table, metric='aitchison') actual = actual.view(skbio.DistanceMatrix) expected = skbio.DistanceMatrix([[0.0000000, 0.4901290, 0.6935510], [0.4901290, 0.0000000, 0.2034219], [0.6935510, 0.2034219, 0.0000000]], ids=['S1', 'S2', 'S3']) self.assertEqual(actual.ids, expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], expected[id1, id2]) def test_canberra_adkins(self): t = biom.Table(np.array([[0, 0], [0, 1], [1, 2]]), ['O1', 'O2', 'O3'], ['S1', 'S2']) t = Artifact.import_data('FeatureTable[Frequency]', t) # expected calculated by hand expected = skbio.DistanceMatrix(np.array([[0.0, 0.66666666666], [0.66666666666, 0.0]]), ids=['S1', 'S2']) actual, = self.method(table=t, metric='canberra_adkins') actual = actual.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], expected[id1, id2]) def test_beta_canberra_adkins_negative_values(self): t = biom.Table(np.array([[0, 0], [0, 1], [-1, -2]]), ['O1', 'O2', 'O3'], ['S1', 'S2']) t = Artifact.import_data('FeatureTable[Frequency]', t) with self.assertRaisesRegex(ValueError, '.*negative values'): self.method(table=t, metric='canberra_adkins') def test_jensenshannon(self): # expected computed with scipy.spatial.distance.jensenshannon expected = skbio.DistanceMatrix([[0.0000000, 0.4645014, 0.52379239], [0.4645014, 0.0000000, 0.07112939], [0.52379239, 0.07112939, 0.0000000]], ids=['S1', 'S2', 'S3']) actual, = self.method(table=self.table, metric='jensenshannon') actual = actual.view(skbio.DistanceMatrix) self.assertEqual(actual.ids, expected.ids) for id1 in actual.ids: for id2 in actual.ids: npt.assert_almost_equal(actual[id1, id2], expected[id1, id2]) class BetaPhylogeneticPassthroughTests(TestPluginBase): package = 'q2_diversity_lib.tests' def artifact(self, semantic_type, fn): return Artifact.import_data(semantic_type, self.get_data_path(fn)) def setUp(self): super().setUp() self.fn = self.plugin.actions['beta_phylogenetic_passthrough'] self.tbl = self.artifact('FeatureTable[Frequency]', 'crawford.biom') self.tre = self.artifact('Phylogeny[Rooted]', 'crawford.nwk') def test_method(self): for metric in METRICS['PHYLO']['UNIMPL']: obs_art, = self.fn(table=self.tbl, phylogeny=self.tre, metric=metric) obs = obs_art.view(skbio.DistanceMatrix) self.assertEqual(9, len(obs.ids)) self.assertEqual((9, 9), obs.shape) # variance adjusted for metric in METRICS['PHYLO']['UNIMPL']: obs_art, = self.fn(table=self.tbl, phylogeny=self.tre, metric=metric, variance_adjusted=True) obs = obs_art.view(skbio.DistanceMatrix) self.assertEqual(9, len(obs.ids)) self.assertEqual((9, 9), obs.shape) # bypass tips for metric in METRICS['PHYLO']['UNIMPL']: obs_art, = self.fn(table=self.tbl, phylogeny=self.tre, metric=metric, bypass_tips=True) obs = obs_art.view(skbio.DistanceMatrix) self.assertEqual(9, len(obs.ids)) self.assertEqual((9, 9), obs.shape) def test_passed_empty_table(self): tbl = Artifact.import_data( 'FeatureTable[Frequency]', biom.Table(np.array([]), [], [])) for metric in METRICS['PHYLO']['UNIMPL']: with self.assertRaisesRegex(ValueError, 'empty'): self.fn(table=tbl, phylogeny=self.tre, metric=metric) def test_passed_bad_metric(self): with self.assertRaisesRegex(TypeError, 'imaginary.*incompatible'): self.fn(table=self.tbl, phylogeny=self.tre, metric='imaginary') def test_beta_phylogenetic_alpha_on_non_generalized(self): with self.assertRaisesRegex(ValueError, 'The alpha parameter is only ' 'allowed when the selected metric is ' '\'generalized_unifrac\''): self.fn(table=self.tbl, phylogeny=self.tre, metric='unweighted_unifrac', alpha=0.11) q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/test_examples.py000066400000000000000000000010451462552567500247360ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from qiime2.plugin.testing import TestPluginBase class UsageExampleTests(TestPluginBase): package = 'q2_diversity_lib.tests' def test_usage_examples(self): self.execute_examples() q2-diversity-lib-2024.5.0/q2_diversity_lib/tests/test_util.py000066400000000000000000000301361462552567500241000ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from unittest import mock import numpy as np import numpy.testing as npt import biom import psutil from qiime2 import Artifact from qiime2.plugin.testing import TestPluginBase from q2_types.feature_table import BIOMV210Format from q2_types.tree import NewickFormat from .._util import (_validate_requested_cpus, _partition, _validate_tables) class PartitionTests(TestPluginBase): package = 'q2_diversity_lib.tests' def test_table_below_batch_size(self): tab = biom.example_table.copy() partitions = list(_partition(tab, block_size=10)) self.assertEqual(len(partitions), 1) self.assertEqual(tab, partitions[0]) def test_table_above_batch_size(self): tab = biom.example_table.copy() partitions = list(_partition(tab, block_size=2)) self.assertEqual(len(partitions), 2) npt.assert_equal(partitions[0].ids(), tab.ids()[:2]) npt.assert_equal(partitions[1].ids(), tab.ids()[2]) def test_table_equal_to_batch_size(self): tab = biom.example_table.copy() partitions = list(_partition(tab, block_size=3)) self.assertEqual(len(partitions), 1) self.assertEqual(tab, partitions[0]) class ValidateTablesTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() self.empty_table = biom.Table(np.array([]), [], []) # empty table generated from self.empty_table with biom v2.1.7 empty_table_fp = self.get_data_path('empty_table.biom') self.empty_table_as_BIOMV210Format = BIOMV210Format(empty_table_fp, mode='r') valid_table_fp = self.get_data_path('crawford.biom') self.valid_table_as_BIOMV210Format = BIOMV210Format(valid_table_fp, mode='r') not_a_table_fp = self.get_data_path('crawford.nwk') self.invalid_view_type = NewickFormat(not_a_table_fp, mode='r') self.valid_table_list = [self.valid_table_as_BIOMV210Format, self.valid_table_as_BIOMV210Format] self.invalid_table_list = [self.valid_table_as_BIOMV210Format, self.invalid_view_type] self.has_empty_table_list = [self.empty_table_as_BIOMV210Format, self.valid_table_as_BIOMV210Format] self.has_nan = biom.Table(np.array([[np.nan, 0, 1], [2, 3, 4]]), ['a', 'b'], ['x', 'y', 'z']) self.has_neg = biom.Table(np.array([[-1, 0, 1], [2, 3, 4]]), ['a', 'b'], ['x', 'y', 'z']) @_validate_tables def f1(table: biom.Table): pass self.function_with_table_param = f1 @_validate_tables def f2(): pass self.function_without_table_param = f2 def test_pass_table_with_nan(self): with self.assertRaisesRegex(ValueError, "table.*contains NaN"): self.function_with_table_param(self.has_nan) def test_pass_table_with_negative_values(self): with self.assertRaisesRegex(ValueError, "table.*contains negative values"): self.function_with_table_param(self.has_neg) def test_pass_empty_table_positionally(self): with self.assertRaisesRegex(ValueError, "table.*is empty"): self.function_with_table_param(self.empty_table_as_BIOMV210Format) def test_pass_empty_table_positionally_list(self): with self.assertRaisesRegex(ValueError, "table.*is empty"): self.function_with_table_param(self.has_empty_table_list) def test_pass_empty_table_as_kwarg(self): with self.assertRaisesRegex(ValueError, "table.*is empty"): self.function_with_table_param( table=self.empty_table_as_BIOMV210Format) def test_pass_empty_table_as_kwarg_list(self): with self.assertRaisesRegex(ValueError, "table.*is empty"): self.function_with_table_param( table=self.has_empty_table_list) def test_decorated_lambda_with_table_param(self): with self.assertRaisesRegex(ValueError, "table.*is empty"): decorated_lambda = _validate_tables(lambda table: None) decorated_lambda(self.empty_table_as_BIOMV210Format) def test_decorated_lambda_with_table_param_list(self): with self.assertRaisesRegex(ValueError, "table.*is empty"): decorated_lambda = _validate_tables(lambda table: None) decorated_lambda(self.has_empty_table_list) def test_wrapped_function_has_no_table_param(self): with self.assertRaisesRegex(TypeError, "missing argument.*table"): self.function_without_table_param() def test_passed_invalid_view_type(self): with self.assertRaisesRegex( ValueError, "Invalid view type.*Newick"): self.function_with_table_param(table=self.invalid_view_type) def test_passed_invalid_view_type_list(self): with self.assertRaisesRegex( ValueError, "Invalid view type.*Newick"): self.function_with_table_param(table=self.invalid_table_list) class ValidateRequestedCPUsTests(TestPluginBase): package = 'q2_diversity_lib.tests' def setUp(self): super().setUp() @_validate_requested_cpus def function_no_params(): pass self.function_no_params = function_no_params @_validate_requested_cpus def function_w_param(n_jobs=3): return n_jobs self.function_w_n_jobs_param = function_w_param @_validate_requested_cpus def function_w_threads(threads=2): return threads self.function_w_threads_param = function_w_threads @_validate_requested_cpus def function_w_duplicate_params(n_jobs=3, threads=2): pass self.function_w_both = function_w_duplicate_params self.jaccard_thru_framework = self.plugin.actions['jaccard'] self.unweighted_unifrac_thru_framework = self.plugin.actions[ 'unweighted_unifrac'] two_feature_table_fp = self.get_data_path('two_feature_table.biom') self.two_feature_table = biom.load_table(two_feature_table_fp) self.two_feature_table_as_BIOMV210Format = BIOMV210Format( two_feature_table_fp, mode='r') self.two_feature_table_as_artifact = Artifact.import_data( 'FeatureTable[Frequency]', two_feature_table_fp) larger_table_fp = self.get_data_path('crawford.biom') self.larger_table_as_artifact = Artifact.import_data( 'FeatureTable[Frequency]', larger_table_fp) valid_tree_fp = self.get_data_path('three_feature.tree') self.valid_tree_as_NewickFormat = NewickFormat(valid_tree_fp, mode='r') self.valid_tree_as_artifact = Artifact.import_data( 'Phylogeny[Rooted]', valid_tree_fp) larger_tree_fp = self.get_data_path('crawford.nwk') self.larger_tree_as_artifact = Artifact.import_data( 'Phylogeny[Rooted]', larger_tree_fp) def test_function_without_cpu_request_param(self): with self.assertRaisesRegex(TypeError, 'without.*n_jobs.*threads'): self.function_no_params() @mock.patch("q2_diversity_lib._util.psutil.Process") def test_function_with_appropriate_param(self, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(return_value=[0, 1, 2]) self.assertEqual(self.function_w_n_jobs_param(3), 3) mock_process.cpu_affinity = mock.MagicMock(return_value=[4, 19]) self.assertEqual(self.function_w_threads_param(2), 2) def test_function_with_duplicate_cpu_allocation_params(self): with self.assertRaisesRegex(TypeError, 'Duplicate parameters'): self.function_w_both() @mock.patch("q2_diversity_lib._util.psutil.Process") @mock.patch('psutil.cpu_count', return_value=999) def test_system_has_no_cpu_affinity(self, mock_cpu_count, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(side_effect=AttributeError) self.assertEqual(self.function_w_n_jobs_param(999), 999) assert mock_process.cpu_affinity.called @mock.patch("q2_diversity_lib._util.psutil.Process") def test_requested_more_than_system_cpus(self, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(return_value=[0, 1, 2]) with self.assertRaisesRegex(ValueError, "\'n_jobs\' cannot exceed"): self.function_w_n_jobs_param(999) with self.assertRaisesRegex(ValueError, "\'threads\' cannot exceed"): self.function_w_threads_param(999) @mock.patch("q2_diversity_lib._util.psutil.Process") def test_requested_cpus_passed_as_kwarg(self, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(return_value=[0, 1, 2]) self.assertEqual(self.function_w_n_jobs_param(n_jobs=3), 3) @mock.patch("q2_diversity_lib._util.psutil.Process") def test_n_jobs_passed_as_default(self, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(return_value=[0, 1, 2]) self.assertEqual(self.function_w_n_jobs_param(), 3) @mock.patch("q2_diversity_lib._util.psutil.Process") def test_zero_passed_to_cpu_request(self, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(return_value=[0, 1, 2]) self.assertEqual(self.function_w_n_jobs_param(0), 3) self.assertEqual(self.function_w_n_jobs_param(n_jobs=0), 3) self.assertEqual(self.function_w_threads_param(0), 3) self.assertEqual(self.function_w_threads_param(threads=0), 3) @mock.patch("q2_diversity_lib._util.psutil.Process") def test_cpu_request_through_framework(self, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(return_value=[0]) self.jaccard_thru_framework(self.larger_table_as_artifact, n_jobs=1) self.jaccard_thru_framework(self.larger_table_as_artifact, n_jobs='auto') self.unweighted_unifrac_thru_framework(self.larger_table_as_artifact, self.larger_tree_as_artifact, threads=1) self.unweighted_unifrac_thru_framework(self.larger_table_as_artifact, self.larger_tree_as_artifact, threads='auto') # If we get here, then it ran without error self.assertTrue(True) @mock.patch("q2_diversity_lib._util.psutil.Process") def test_more_threads_than_max_stripes(self, mock_process): mock_process = psutil.Process() mock_process.cpu_affinity = mock.MagicMock(return_value=[0]) # The two_feature_table used here has only three samples, meaning # that it has a max of (3+1)/2 = 2 stripes. Unifrac may report # requests of more-threads-than-stripes to stderror, but should handle # that situation gracefully. self.unweighted_unifrac_thru_framework( self.two_feature_table_as_artifact, self.valid_tree_as_artifact, threads=1) self.unweighted_unifrac_thru_framework( self.two_feature_table_as_artifact, self.valid_tree_as_artifact, threads='auto') # If we get here, then it ran without error self.assertTrue(True) q2-diversity-lib-2024.5.0/setup.cfg000066400000000000000000000002671462552567500167210ustar00rootroot00000000000000[versioneer] VCS=git style=pep440 versionfile_source = q2_diversity_lib/_version.py versionfile_build = q2_diversity_lib/_version.py tag_prefix = parentdir_prefix = q2-diversity-lib- q2-diversity-lib-2024.5.0/setup.py000066400000000000000000000017601462552567500166110ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # Copyright (c) 2018-2023, QIIME 2 development team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ---------------------------------------------------------------------------- from setuptools import setup, find_packages import versioneer setup( name="q2-diversity-lib", version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), packages=find_packages(), author="Chris Keefe", author_email="crk239@nau.edu", description="Utility exposing diversity metrics/measures as Actions", entry_points={ "qiime2.plugins": ["q2-diversity-lib=q2_diversity_lib.plugin_setup:plugin"] }, url="https://qiime2.org", license="BSD-3-Clause", package_data={ 'q2_diversity_lib': ['citations.bib'], 'q2_diversity_lib.tests': ['data/*'] }, zip_safe=False, ) q2-diversity-lib-2024.5.0/versioneer.py000066400000000000000000002060221462552567500176300ustar00rootroot00000000000000 # Version: 0.18 # flake8: noqa """The Versioneer - like a rocketeer, but for versions. The Versioneer ============== * like a rocketeer, but for versions! * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain * Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy * [![Latest Version] (https://pypip.in/version/versioneer/badge.svg?style=flat) ](https://pypi.python.org/pypi/versioneer/) * [![Build Status] (https://travis-ci.org/warner/python-versioneer.png?branch=master) ](https://travis-ci.org/warner/python-versioneer) This is a tool for managing a recorded version number in distutils-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control system, and maybe making new tarballs. ## Quick Install * `pip install versioneer` to somewhere to your $PATH * add a `[versioneer]` section to your setup.cfg (see below) * run `versioneer install` in your source tree, commit the results ## Version Identifiers Source trees come from a variety of places: * a version-control system checkout (mostly used by developers) * a nightly tarball, produced by build automation * a snapshot tarball, produced by a web-based VCS browser, like github's "tarball from tag" feature * a release tarball, produced by "setup.py sdist", distributed through PyPI Within each source tree, the version identifier (either a string or a number, this tool is format-agnostic) can come from a variety of places: * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows about recent "tags" and an absolute revision-id * the name of the directory into which the tarball was unpacked * an expanded VCS keyword ($Id$, etc) * a `_version.py` created by some earlier build step For released software, the version identifier is closely related to a VCS tag. Some projects use tag names that include more than just the version string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool needs to strip the tag prefix to extract the version identifier. For unreleased software (between tags), the version identifier should provide enough information to help developers recreate the same tree, while also giving them an idea of roughly how old the tree is (after version 1.2, before version 1.3). Many VCS systems can report a description that captures this, for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has uncommitted changes. The version identifier is used for multiple purposes: * to allow the module to self-identify its version: `myproject.__version__` * to choose a name and prefix for a 'setup.py sdist' tarball ## Theory of Operation Versioneer works by adding a special `_version.py` file into your source tree, where your `__init__.py` can import it. This `_version.py` knows how to dynamically ask the VCS tool for version information at import time. `_version.py` also contains `$Revision$` markers, and the installation process marks `_version.py` to have this marker rewritten with a tag name during the `git archive` command. As a result, generated tarballs will contain enough information to get the proper version. To allow `setup.py` to compute a version too, a `versioneer.py` is added to the top level of your source tree, next to `setup.py` and the `setup.cfg` that configures it. This overrides several distutils/setuptools commands to compute the version when invoked, and changes `setup.py build` and `setup.py sdist` to replace `_version.py` with a small static file that contains just the generated version data. ## Installation See [INSTALL.md](./INSTALL.md) for detailed installation instructions. ## Version-String Flavors Code which uses Versioneer can learn about its version string at runtime by importing `_version` from your main `__init__.py` file and running the `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can import the top-level `versioneer.py` and run `get_versions()`. Both functions return a dictionary with different flavors of version information: * `['version']`: A condensed version string, rendered using the selected style. This is the most commonly used value for the project's version string. The default "pep440" style yields strings like `0.11`, `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section below for alternative styles. * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the commit date in ISO 8601 format. This will be None if the date is not available. * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None * `['error']`: if the version string could not be computed, this will be set to a string describing the problem, otherwise it will be None. It may be useful to throw an exception in setup.py if this is set, to avoid e.g. creating tarballs with a version string of "unknown". Some variants are more useful than others. Including `full-revisionid` in a bug report should allow developers to reconstruct the exact code being tested (or indicate the presence of local changes that should be shared with the developers). `version` is suitable for display in an "about" box or a CLI `--version` output: it can be easily compared against release notes and lists of bugs fixed in various releases. The installer adds the following text to your `__init__.py` to place a basic version in `YOURPROJECT.__version__`: from ._version import get_versions __version__ = get_versions()['version'] del get_versions ## Styles The setup.cfg `style=` configuration controls how the VCS information is rendered into a version string. The default style, "pep440", produces a PEP440-compliant string, equal to the un-prefixed tag name for actual releases, and containing an additional "local version" section with more detail for in-between builds. For Git, this is TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and that this commit is two revisions ("+2") beyond the "0.11" tag. For released software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". Other styles are available. See [details.md](details.md) in the Versioneer source tree for descriptions. ## Debugging Versioneer tries to avoid fatal errors: if something goes wrong, it will tend to return a version of "0+unknown". To investigate the problem, run `setup.py version`, which will run the version-lookup code in a verbose mode, and will display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). ## Known Limitations Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github [issues page](https://github.com/warner/python-versioneer/issues). ### Subprojects Versioneer has limited support for source trees in which `setup.py` is not in the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are two common reasons why `setup.py` might not be in the root: * Source trees which contain multiple subprojects, such as [Buildbot](https://github.com/buildbot/buildbot), which contains both "master" and "slave" subprojects, each with their own `setup.py`, `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also provide bindings to Python (and perhaps other langauges) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs and implementation details which frequently cause `pip install .` from a subproject directory to fail to find a correct version string (so it usually defaults to `0+unknown`). `pip install --editable .` should work correctly. `setup.py install` might work too. Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. [Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking this issue. The discussion in [PR #61](https://github.com/warner/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve pip to let Versioneer work correctly. Versioneer-0.16 and earlier only looked for a `.git` directory next to the `setup.cfg`, so subprojects were completely unsupported with those releases. ### Editable installs with setuptools <= 18.5 `setup.py develop` and `pip install --editable .` allow you to install a project into a virtualenv once, then continue editing the source code (and test) without re-installing after every change. "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a convenient way to specify executable scripts that should be installed along with the python package. These both work as expected when using modern setuptools. When using setuptools-18.5 or earlier, however, certain operations will cause `pkg_resources.DistributionNotFound` errors when running the entrypoint script, which must be resolved by re-installing the package. This happens when the install happens with one version, then the egg_info data is regenerated while a different version is checked out. Many setup.py commands cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. [Bug #83](https://github.com/warner/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. ### Unicode version strings While Versioneer works (and is continually tested) with both Python 2 and Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. Newer releases probably generate unicode version strings on py2. It's not clear that this is wrong, but it may be surprising for applications when then write these strings to a network connection or include them in bytes-oriented APIs like cryptographic checksums. [Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates this question. ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) * edit `setup.cfg`, if necessary, to include any new configuration settings indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. * re-run `versioneer install` in your source tree, to replace `SRC/_version.py` * commit any changed files ## Future Directions This tool is designed to make it easily extended to other version-control systems: all VCS-specific components are in separate directories like src/git/ . The top-level `versioneer.py` script is assembled from these components by running make-versioneer.py . In the future, make-versioneer.py will take a VCS name as an argument, and will construct a version of `versioneer.py` that is specific to the given VCS. It might also take the configuration arguments that are currently provided manually during installation by editing setup.py . Alternatively, it might go the other direction and include code from all supported VCS systems, reducing the number of intermediate scripts. ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. Specifically, both are released under the Creative Commons "Public Domain Dedication" license (CC0-1.0), as described in https://creativecommons.org/publicdomain/zero/1.0/ . """ from __future__ import print_function try: import configparser except ImportError: import ConfigParser as configparser import errno import json import os import re import subprocess import sys class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_root(): """Get the project root directory. We require that all commands are run from the project root, i.e. the directory that contains setup.py, setup.cfg, and versioneer.py . """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " "or in a way that lets it use sys.argv[0] to find the root " "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools # tree) execute all dependencies in a single python process, so # "versioneer" may be imported multiple times, and python's shared # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. me = os.path.realpath(os.path.abspath(__file__)) me_dir = os.path.normcase(os.path.splitext(me)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir: print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(me), versioneer_py)) except NameError: pass return root def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" # This might raise EnvironmentError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") parser = configparser.SafeConfigParser() with open(setup_cfg, "r") as f: parser.readfp(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name): if parser.has_option("versioneer", name): return parser.get("versioneer", name) return None cfg = VersioneerConfig() cfg.VCS = VCS cfg.style = get(parser, "style") or "" cfg.versionfile_source = get(parser, "versionfile_source") cfg.versionfile_build = get(parser, "versionfile_build") cfg.tag_prefix = get(parser, "tag_prefix") if cfg.tag_prefix in ("''", '""'): cfg.tag_prefix = "" cfg.parentdir_prefix = get(parser, "parentdir_prefix") cfg.verbose = get(parser, "verbose") return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, p.returncode return stdout, p.returncode LONG_VERSION_PY['git'] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.18 (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "%(STYLE)s" cfg.tag_prefix = "%(TAG_PREFIX)s" cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %%s" %% dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) return None, p.returncode return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %%s" %% r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%%s*" %% tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%%s' doesn't start with prefix '%%s'" print(fmt %% (full_tag, tag_prefix)) pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" %% (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%%d" %% pieces["distance"] else: # exception #1 rendered = "0.post.dev%%d" %% pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def do_vcs_install(manifest_in, versionfile_source, ipy): """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py for export-subst keyword substitution. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] files = [manifest_in, versionfile_source] if ipy: files.append(ipy) try: me = __file__ if me.endswith(".pyc") or me.endswith(".pyo"): me = os.path.splitext(me)[0] + ".py" versioneer_file = os.path.relpath(me) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: f = open(".gitattributes", "r") for line in f.readlines(): if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: present = True f.close() except EnvironmentError: pass if not present: f = open(".gitattributes", "a+") f.write("%s export-subst\n" % versionfile_source) f.close() files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") SHORT_VERSION_PY = """ # This file was generated by 'versioneer.py' (0.18) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json version_json = ''' %s ''' # END VERSION_JSON def get_versions(): return json.loads(version_json) """ def versions_from_file(filename): """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except EnvironmentError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename, versions): """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] root = get_root() cfg = get_config_from_root(root) assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or cfg.verbose assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) # extract version from first of: _version.py, VCS command (e.g. 'git # describe'), parentdir. This is meant to work for developers using a # source checkout, for users of a tarball created by 'setup.py sdist', # and for users of a tarball/zipball created by 'git archive' or github's # download-from-tag feature or the equivalent in other VCSes. get_keywords_f = handlers.get("get_keywords") from_keywords_f = handlers.get("keywords") if get_keywords_f and from_keywords_f: try: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: print("got version from expanded keyword %s" % ver) return ver except NotThisMethod: pass try: ver = versions_from_file(versionfile_abs) if verbose: print("got version from file %s %s" % (versionfile_abs, ver)) return ver except NotThisMethod: pass from_vcs_f = handlers.get("pieces_from_vcs") if from_vcs_f: try: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: print("got version from VCS %s" % ver) return ver except NotThisMethod: pass try: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: print("got version from parentdir %s" % ver) return ver except NotThisMethod: pass if verbose: print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} def get_version(): """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(): """Get the custom setuptools/distutils subclasses used by Versioneer.""" if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and # 'easy_install .'), in which subdependencies of the main project are # built (using setup.py bdist_egg) in the same python process. Assume # a main project A and a dependency B, which use different versions # of Versioneer. A's setup.py imports A's Versioneer, leaving it in # sys.modules by the time B's setup.py is executed, causing B to run # with the wrong versioneer. Setuptools wraps the sub-dep builds in a # sandbox that restores sys.modules to it's pre-build state, so the # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. # Also see https://github.com/warner/python-versioneer/issues/52 cmds = {} # we add "version" to both distutils and setuptools from distutils.core import Command class cmd_version(Command): description = "report generated version string" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version # we override "build_py" in both distutils and setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py # distutils/install -> distutils/build ->.. # setuptools/bdist_wheel -> distutils/install ->.. # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? # pip install: # copies source tree to a tempdir before running egg_info/etc # if .git isn't copied too, 'git describe' will fail # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? # we override different "build_py" commands for both environments if "setuptools" in sys.modules: from setuptools.command.build_py import build_py as _build_py else: from distutils.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION # "product_version": versioneer.get_version(), # ... class cmd_build_exe(_build_exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _build_exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? try: from py2exe.distutils_buildexe import py2exe as _py2exe # py3 except ImportError: from py2exe.build_exe import py2exe as _py2exe # py2 class cmd_py2exe(_py2exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _py2exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["py2exe"] = cmd_py2exe # we override different "sdist" commands for both environments if "setuptools" in sys.modules: from setuptools.command.sdist import sdist as _sdist else: from distutils.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self): versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old # version self.distribution.metadata.version = versions["version"] return _sdist.run(self) def make_release_tree(self, base_dir, files): root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) # now locate _version.py in the new base_dir directory # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds CONFIG_ERROR = """ setup.cfg is missing the necessary Versioneer configuration. You need a section like: [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: import versioneer setup(version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ...) Please read the docstring in ./versioneer.py for configuration instructions, edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. """ SAMPLE_CONFIG = """ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] #VCS = git #style = pep440 #versionfile_source = #versionfile_build = #tag_prefix = #parentdir_prefix = """ INIT_PY_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ def do_setup(): """Main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) except (EnvironmentError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (EnvironmentError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) return 1 print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() except EnvironmentError: old = "" if INIT_PY_SNIPPET not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: f.write(INIT_PY_SNIPPET) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) ipy = None # Make sure both the top-level "versioneer.py" and versionfile_source # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so # they'll be copied into source distributions. Pip won't be able to # install the package without this. manifest_in = os.path.join(root, "MANIFEST.in") simple_includes = set() try: with open(manifest_in, "r") as f: for line in f: if line.startswith("include "): for include in line.split()[1:]: simple_includes.add(include) except EnvironmentError: pass # That doesn't cover everything MANIFEST.in can do # (http://docs.python.org/2/distutils/sourcedist.html#commands), so # it might give some false negatives. Appending redundant 'include' # lines is safe, though. if "versioneer.py" not in simple_includes: print(" appending 'versioneer.py' to MANIFEST.in") with open(manifest_in, "a") as f: f.write("include versioneer.py\n") else: print(" 'versioneer.py' already in MANIFEST.in") if cfg.versionfile_source not in simple_includes: print(" appending versionfile_source ('%s') to MANIFEST.in" % cfg.versionfile_source) with open(manifest_in, "a") as f: f.write("include %s\n" % cfg.versionfile_source) else: print(" versionfile_source already in MANIFEST.in") # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. do_vcs_install(manifest_in, cfg.versionfile_source, ipy) return 0 def scan_setup_py(): """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 with open("setup.py", "r") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import") if "versioneer.get_cmdclass()" in line: found.add("cmdclass") if "versioneer.get_version()" in line: found.add("get_version") if "versioneer.VCS" in line: setters = True if "versioneer.versionfile_source" in line: setters = True if len(found) != 3: print("") print("Your setup.py appears to be missing some important items") print("(but I might be wrong). Please make sure it has something") print("roughly like the following:") print("") print(" import versioneer") print(" setup( version=versioneer.get_version(),") print(" cmdclass=versioneer.get_cmdclass(), ...)") print("") errors += 1 if setters: print("You should remove lines like 'versioneer.VCS = ' and") print("'versioneer.versionfile_source = ' . This configuration") print("now lives in setup.cfg, and should be removed from setup.py") print("") errors += 1 return errors if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": errors = do_setup() errors += scan_setup_py() if errors: sys.exit(1)