From 75aff2046d409a9bc1c87a7d7a126c9ae87b619a Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 1 Dec 2023 11:53:14 -0600 Subject: [PATCH 001/178] Update transform_detect_color_card.md add link to tutorial page --- docs/transform_detect_color_card.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/transform_detect_color_card.md b/docs/transform_detect_color_card.md index e57d34744..8674432b8 100644 --- a/docs/transform_detect_color_card.md +++ b/docs/transform_detect_color_card.md @@ -15,6 +15,8 @@ Automatically detects a color card and creates a labeled mask. - radius - Radius of circle to make the color card labeled mask (default = 20). - **Returns** - labeled_mask - Labeled color card mask (useful downstream of this step in `pcv.transform.get_color_matrix` and `pcv.transform.correct_color`) +- **Example use:** + - [Color Correction Tutorial](tutorials/transform_color_correction_tutorial.md) !!! note Color chip size can only be used reasonably as a scaling factor (converting pixels to a known real world scale like cms) From bef9964b376f7680fb5031dcd24281952efa13ae Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 1 Dec 2023 11:53:50 -0600 Subject: [PATCH 002/178] Update transform_affine_color_correction.md --- docs/transform_affine_color_correction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/transform_affine_color_correction.md b/docs/transform_affine_color_correction.md index 6320e64af..3ccfb5b11 100644 --- a/docs/transform_affine_color_correction.md +++ b/docs/transform_affine_color_correction.md @@ -15,7 +15,8 @@ Euclidean distance between the transformed source color values and the target co - rgb_img - an RGB image with color chips visualized - source_matrix - array of RGB color values (intensity in the range [0-1]) from the image to be corrected where each row is one color reference and the columns are organized as index,R,G,B - target_matrix - array of target RGB color values (intensity in the range [0-1]) where each row is one color reference and the columns are organized as index,R,G,B - +- **Example use:** + - [Color Correction Tutorial](tutorials/transform_color_correction_tutorial.md) **Reference Images** From ab3b01fe265c799f7edf5d931a1d404565c9dda5 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 1 Dec 2023 15:03:46 -0600 Subject: [PATCH 003/178] Update analyze_grayscale.md typo of trait variable name --- docs/analyze_grayscale.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/analyze_grayscale.md b/docs/analyze_grayscale.md index 82a986a94..90f093999 100644 --- a/docs/analyze_grayscale.md +++ b/docs/analyze_grayscale.md @@ -18,7 +18,7 @@ the values out to the [Outputs class](outputs.md). Can also return/plot/print ou - **Example use:** * [Grayscale Image Tutorial](tutorials/grayscale_tutorial.md) -- **Output data stored:** Data ('gray_frequencies', 'gray_mean', 'gray_median', 'nir_stdev') automatically gets stored to +- **Output data stored:** Data ('gray_frequencies', 'gray_mean', 'gray_median', 'gray_stdev') automatically gets stored to the [`Outputs` class](outputs.md) when this function is ran. These data can always get accessed during a workflow (example below). For more detail about data output see [Summary of Output Observations](output_measurements.md#summary-of-output-observations) From e0a2ddb542f6a97c02a0e41a4452b1fd7fa953dc Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 1 Dec 2023 15:09:49 -0600 Subject: [PATCH 004/178] Update get_color_matrix.md add link to detect_color_card doc page --- docs/get_color_matrix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/get_color_matrix.md b/docs/get_color_matrix.md index 8a15b70e4..6cac913e0 100644 --- a/docs/get_color_matrix.md +++ b/docs/get_color_matrix.md @@ -8,7 +8,7 @@ Computes the average *R*, *G*, *B* values for each region in the RGB image denot - **Parameters** - rgb_img - RGB image with color chips visualized - - mask - a gray-scale img with unique values for each segmented space, representing unique, discrete color chips. + - mask - a gray-scale img with unique values for each segmented space, representing unique, discrete color chips. Likely created with [`pcv.transform.detect_color_card`](transform_detect_color_card.md). - **Returns** - color_matrix - a *n* x 4 matrix containing the average red value, average green value, and average blue value for each color chip. From 39e8e9a5d4bca0d84210a71189e3cb70a264f9e7 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 1 Dec 2023 15:25:48 -0600 Subject: [PATCH 005/178] Update reassign_frame_labels.py add file description --- plantcv/plantcv/photosynthesis/reassign_frame_labels.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plantcv/plantcv/photosynthesis/reassign_frame_labels.py b/plantcv/plantcv/photosynthesis/reassign_frame_labels.py index 0d427594a..ee7e725ea 100644 --- a/plantcv/plantcv/photosynthesis/reassign_frame_labels.py +++ b/plantcv/plantcv/photosynthesis/reassign_frame_labels.py @@ -1,3 +1,4 @@ +"""Reassign PSII frame labels based on induction curve""" import numpy as np from plantcv.plantcv import fatal_error from plantcv.plantcv.classes import PSII_data From 948979b8d72bbd9d53068119b56390e6e675105f Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 1 Dec 2023 15:44:10 -0600 Subject: [PATCH 006/178] Update output_measurements.md add spaces for readability and add missing measurements --- docs/output_measurements.md | 41 ++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/output_measurements.md b/docs/output_measurements.md index f15c0016d..92dde927b 100644 --- a/docs/output_measurements.md +++ b/docs/output_measurements.md @@ -147,32 +147,35 @@ suggestions for additional metadata we should track that would be useful to you, Functions that automatically store data to the [`Outputs` class](outputs.md) are [analyze.bound_horizontal](analyze_bound_horizontal2.md), -[analyze.bound_vertical](analyze_bound_vertical2.md), -[analyze.color](analyze_color2.md), -[analyze.grayscale](analyze_grayscale.md), -[analyze.size](analyze_size.md), -[analyze.spectral_index](analyze_spectral_index.md), -[analyze.spectral_reflectance](analyze_spectral_reflectance.md), -[analyze.thermal](analyze_thermal.md), +[analyze.bound_vertical](analyze_bound_vertical2.md), +[analyze.color](analyze_color2.md), +[analyze.grayscale](analyze_grayscale.md), +[analyze.size](analyze_size.md), +[analyze.spectral_index](analyze_spectral_index.md), +[analyze.spectral_reflectance](analyze_spectral_reflectance.md), +[analyze.thermal](analyze_thermal.md), [analyze.yii](analyze_yii.md), -[analyze.npq](analyze_npq.md), -[homology.landmark_reference_pt_dist](homology_landmark_reference_pt_dist.md), +[analyze.npq](analyze_npq.md), +[homology.acute](homology_landmark_reference_pt_dist.md), +[homology.landmark_reference_pt_dist](homology_landmark_reference_pt_dist.md), [homology.x_axis_pseudolandmarks](homology_x_axis_pseudolandmarks.md), -[homology.y_axis_pseudolandmarks](homology_y_axis_pseudolandmarks.md), -[morphology.check_cycles](check_cycles.md), +[homology.y_axis_pseudolandmarks](homology_y_axis_pseudolandmarks.md), +[morphology.analyze_stem](analyze_stem.md), +[morphology.check_cycles](check_cycles.md), [morphology.fill_segments](fill_segments.md), -[morphology.find_tips](find_tips.md), +[morphology.find_tips](find_tips.md), [morphology.find_branch_pts](find_branch_pts.md), -[morphology.segment_angle](segment_angle.md), +[morphology.segment_angle](segment_angle.md), [morphology.segment_curvature](segment_curvature.md), -[morphology.segment_euclidean_length](segment_euclidean_length.md), +[morphology.segment_euclidean_length](segment_euclidean_length.md), [morphology.segment_insertion_angle](segment_insertion_angle.md), -[morphology.segment_path_length](segment_pathlength.md), -[morphology.segment_tangent_angle](segment_tangent_angle.md), -[report_size_marker_area](report_size_marker.md), -[transform.find_color_card](find_color_card.md), +[morphology.segment_path_length](segment_pathlength.md), +[morphology.segment_tangent_angle](segment_tangent_angle.md), +[report_size_marker_area](report_size_marker.md), +[transform.find_color_card](find_color_card.md), +[transform.detect_color_card](transform_detect_color_card.md) [watershed_segmentation](watershed.md), and -[within_frame](within_frame.md) +[within_frame](within_frame.md). All of these functions include an optional `label` parameter that allows users to append custom prefixes to the unique variable identifier. From 565f0799b8108441070cfa34db4b7a87d39da934 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Mon, 4 Dec 2023 11:07:38 -0600 Subject: [PATCH 007/178] Update transform_detect_color_card.md extra examples of grabbing outputs --- docs/transform_detect_color_card.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/transform_detect_color_card.md b/docs/transform_detect_color_card.md index 8674432b8..0bc669979 100644 --- a/docs/transform_detect_color_card.md +++ b/docs/transform_detect_color_card.md @@ -33,6 +33,8 @@ rgb_img, path, filename = pcv.readimage("target_img.png") cc_mask = pcv.transform.detect_color_card(rgb_img=rgb_img) avg_chip_size = pcv.outputs.observations['default']['median_color_chip_size']['value'] +avg_chip_w = pcv.outputs.observations['default']['median_color_chip_width']['value'] +avg_chip_h = pcv.outputs.observations['default']['median_color_chip_height']['value'] ``` From dd1b73d84e847768ba0cae52d50e8823076d9803 Mon Sep 17 00:00:00 2001 From: Noah Fahlgren Date: Tue, 16 Jan 2024 10:32:26 -0600 Subject: [PATCH 008/178] Update PyPi deploy workflow to use OIDC authentication --- .github/workflows/publish-package.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 6a67c1355..914e31a45 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -33,7 +33,11 @@ jobs: deploy: needs: build runs-on: ubuntu-latest - + environment: + name: pypi + url: https://pypi.org/p/plantcv + permissions: + id-token: write steps: - uses: actions/checkout@main with: @@ -46,10 +50,8 @@ jobs: run: | python -m pip install --upgrade pip pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + - name: Build run: | python setup.py sdist bdist_wheel - twine upload dist/* + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 From fdc3c7f045e854a9a349218a47179753c3403b3f Mon Sep 17 00:00:00 2001 From: Malia Gehan Date: Mon, 2 Oct 2023 09:27:58 -0500 Subject: [PATCH 009/178] watershed update altered watershed output so it is the labeled image rather than the matplotlib figure. The output data is not altered, and I changed the debug images so that is the labeled image rather than the distance image. --- .../watershed/watershed-labels.png | Bin 0 -> 24187 bytes docs/watershed.md | 1 + plantcv/plantcv/watershed.py | 12 +++++++----- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 docs/img/documentation_images/watershed/watershed-labels.png diff --git a/docs/img/documentation_images/watershed/watershed-labels.png b/docs/img/documentation_images/watershed/watershed-labels.png new file mode 100644 index 0000000000000000000000000000000000000000..f016a09373011155213d0681906d6621cd365eb6 GIT binary patch literal 24187 zcmdSBgWi({a(CU-$Zp<)&dxV9ut_B*K9}5~`SH}pr-hY&V?|qwpuNZ~s|EI@7(Erp%M;2oIZ<+JHnWV(lci@8KqHO4n zhK5Ic|3yd3&ZPi)GqinT;9;PlE@t8E$Zht@+1!%b$I<1!6`F*P7*KSy^e{vCI664F zi}^@0{8K{=DBnNkVL<#-#lv2b!9YVBA?NI7i4fxE<>qC0jEg`ZB-~zEiRsEK{?{D1 zlVq^*@Ng01;qmtN=Jpogc6PJo;S&`V<>BS$;pgW9YH+#xI(eA+a5=d%{@cm_^doQS zZsBI@;$iFTgt+h5%-q@2Lz02vp3wh({vD@>t=0db{^d;jaK>jlwnuZ?^h~A7*$7#<1rO28Q@u)tCk12~RjP-Mm6aLB zkCsryPVc@VDeUnx5azhaFd2Lq{Gv3nyrvb&?u*}dhN7QES46M(Uw3t0_^xa%SRXe2 zrn<8~yy%|$7}=?D#f|F)E70Ov{*^=^jzS2I`ohSKHVK9s|QiIo}VC6NEoAdZ&804UuP zjDfj;RKPjdZvRmO9b4bi1o}Y0L7Ipd(^KjHYHA{G@&6g%PY9E24-OODfp+BInmsrJ z1^*6}CPW@062cTZfgyU&3P2#_Qs6(nkPviMqHkf5MJbUs2?Pg8IVo>%CSoGk-WMs|0OUrY`map$?a->NlMv#OfKy#ZAY|{~V z)QfHQMS+|k$Farm@^VjCEFD+;u*01v05+mX?MSj5c00{Dv}6A?b%3*xhKxwO;d({;@n6wHgQQuAmVPVy_f8t10NN2*&oBJvenYwk zry(x`}>gy8kK(S3uas2-{h=?*||)3P>@#lB6H| zKLWKa0!Z$Is6}FRJaiMzY_b&EB*yy@axtl6g+z^sg93&lUWbbegF=odEm}^k$w8mF z@25!gzR#%UiZnFz$6cpclL6&U$_K7>wUpvqat!+&`}fQ)Far8VawuOG1m1pm^cS2g zC!ixO1jAthWr-vUhGk#7f>Z9R(1=5`=s^Y7i(5vw`*mK=hQ1DN$zBmL|G55mKVTuq zCBjdr&9B-KAw5}==R7CJ zdk{?Iz=l<(19Zt5-#G;_gLEx=L_xVULjkag6QysgI%qS1$-MMBxW!+GppRe>sgzv6 z$>T)GO#)n5lPO5Ly}-KCg)Wk?G(7iPPY6bPMNI%ncR{04p3h+ zC16G$G4t+)Q~QR)8-85#Tt><&{xZJu&-Jb2GEW0QS-4hb~8YhynY@pd9OUW zyPdg%NYj2LmJHW~*tXxV_%(#29J`pdf2v5Xzs{_`dVOs;Nw|@;DuFtyTw9&yawjdt zuG{VdE0$kW9s_Kb&YiLoDRp->xrEV>@fALT7SeYvZH{kX7EfgG`h2!O&wJUnb=hZ) zKu?k1u!xt!VC7Y+M!XLKG0@t^OYz|ZyFt~z*Zdi1C*r^lj5~u70`WO!)yk*gHRMY_ z?!kbD6Kjg}lErW}j`Ob;$8qCoN&WSp5O&Ia$|*@Wv48b54C+7=&PP(JlQbxCsWlAS z_NYak*_it9lv+I9$9Y2%(g%y)pJP-?Y@)?Jum9onaiN;(Jyh!E=m-_Z6Zz76X%>RX zOy5Xu_UMz6Q@HC+5cn({V~+ zc;d~KXaPie|JQy;PSX|`6Z zacmhDyep71zc)PA*oR+t%YwY5WNCf98gkcl^OdK0#T7g)SLg!PO+xIpS75&xPQwK8 z9b3bLN<>t0j3J!}EtES%AVq@JrO9ckA0N-W;MKB^YL)UCA zlh9(5m%tQBNQQ~Wvrs*0oeS)cfldfZxHMI+Yb|X>s)-fhNxSjfqt5fW=lYPP>pJP$_{9G)=8kOel;cfnekh= zSS$gsSSsKbXOBbCV(Yt0Q*GlUw#};R`f!CP!|fp@*@C;P^g-2*#lt5zr<1}DT8wiK z4j@blg;RJqsbt^#H}KH0>OXaHE3Nltej4Mbb@&MS#HHdGOm4xV#?#q^dB_B38Lf?C zX9YCnM)fz@H@F`k<4Jsa2UZ|qyMFrY<*Xl}Eu7`YOclA_Th)v4bP&LS?voO7hFZwL zdeJdqR?+tff#WGWdgIb+@FKS+?PGC3c{cRVS#U!@vyJ)FV_uJZVa`VDMG{0SDQesc zAFo4Gqmjq%tbnH80gvS?7C=gC46fCAPkKx}zB8O$-fT&&{E$-?Cm6&==jZq|2;UY2 z#+IkJI8zmaM(1PR|4Fm8ke<7Is=P8-ftDm~LHGDBJ3_0sUJ6GfV&9(xQvrB+6q0T& zXhPA`oQ*iXSTvYA3k|VFch3xD(u&chGHTPIn_%nGQFqT^LJH@z;BSVi*^;BkAh8l3 z&=h&hGZ=nKBDrfWCCEN@kMox1V1>M`=q(gtsTV|zTS)rm)1U*UCj-;X;vs~WaGI`f zUhN|TF)7D5W!f+kORLw~@1sO%W8i%@YCqv&)@6|Vc_=ndzvGwF7WWKkCt=r9xyh0l z%k945!MI!uV6NP>aq=Ue@XNo4LCz~%Y{ej}GEspKFK9xX0PSD;-0p+)#!Wxntu`Cc zmwAnBy%4-7FY{-)6%pUS#l0Gxh3_hi4w@I&S0)GL(1>=zMXUZS*pafIry1~$v@ZQf zsGU7q>SmlOfUIXr`-mD^R%MVB`p8&#-+pu3)6$mwu|6k3#_H)%oKUQvAT5mdz0x(wVhiUg-w#WatKJ2C|*D9bjetrIwk-{10 zx2*nlocGFW;hCK}3Dz@@X;{$awDgxXHX=|vGpt-jGf(Uhjr%y?>UaCvUxR0W0f$G- zqRwkB`m1SX{K1LHLEBp2C!TJaK;T6ektJYF6=OsDn8zz%i8{+ZW74dBxAANs>!;P z5AD8QIvf|D{0Put2fKZI6JwNn{B4)lvR>Q%c#vE;AQGRFXxVq`0b`2ioZ;{y*-H-{ zMr=6t@zJkscD?d>;kSpK7sjVbA{pigG~X#7(*y-0+>|ZQ%&h-m=QkmUU2f9@ojwQ! z$8_Id(~(LhDRj>Ao}Dv>qvER8o;a!;>L!1V10#+2m3ip7O0C6Ip$xj4@9zccn%Ef% zkzWngWhQZWA-o#to@9PPDOSEW?D3O37=831^>m%tl zzMOq#B1!Z5?2A?R5K~0q_U|Qc6Z@he3q(EWNv_!Qo|M}G%QUwVa{U^Y_KG|UzTT;9 z%kP{P7%!@}STmzD!hGJXr4&?8md1@@xu>5@+hyCX=QZ_07q=(M;ns~C2(Em`-Dq0T z>Cv;CiHd6uDNs8TtoL(arF>(Bj}ArmR4bV|b8)Zd&Clh8P;olyvl==iXF zXR2eNRH2<|JPY;cJdT8--tV$Nd=Qpx*DhYiE6Tt%j0I2BV@Nr7o~LW@ zIgzb-HON!JSoaE$ZTa70zV(ZNRA3{LAx!K~gd>utWs|GhH?yr8P+}|uS1Pf1s}>3h zRRv*Mt>$4ts}bb;obI|6G4g8`VyUf)Yec_L^fVDTEPPm-3g15z%u7(a?wOhAJ$Z(`lG}2&cD2M$V}T! za1W+jPf1sfvC!3hT}tX*Oz(x4eJnzJaQgomY)9Ax$RDh~k${_UnWuE?PnH(EfeBNX zf6$$LCp9YPX{?d{H>;w$?!t!}@LHxR3SKO~X?SJAOXAgx+D}p+9t-1x02F$8=YQbF z48-P*qE*$jWW)8ELnEarP7zC#0#+1Qt_Hk6A1itCgtxu>%hIbJ>j7B&bxY7_;URjJ zTvrNq8?x@MlQE9YI8zI$ytc^jH{@f(y?2-GYu&q$2K(kOQ!#EnR6F-x)8q22VK(ML zA#PuiH0-f(%I6TKzB_}}CtJ^Pqm9w`^ydxH22E;iNC~ANtMjUPFYzGHHSnAL=)?Q> zhtwNNWj}lk8Caih1j=tF^xMD z2jDe~s~@C0aQ1k5F;7T?LR*#%{{%PYsG#PeEce}vZkDz_Ac$S(BJwnC@b^aM5d(IA zYFd}SPy3vVvXUb>{1DA^wlM3?nu}Q%A zLUxfEsTt0Lq|XE+Mm>#UIq)b(sAEI**~hlqxAOO%M$hiZ&}Ob5@hCj~Rk0jYhVeK) zi`Tg4!dURPY%YziPi*)1#iu;CMhB%TRbm?SL2S+=a-MlYu1J$zxlLvkHdAhS1X_#L zYCDMf9CXDqeuSN#CUu~3RDcn57EjN>^7JT=7J?~2lK}Dhq^OL2!r+vFVgLdJg&$J+ zM*+ncNm3R%6+0LoF^w!v2HgSCDF-g2b8Qlq#EM$`K^$8o8MxR-9#P7wpsbd8eHlk; zX0o&W__(y@CR(c#eYSa8ZB6}(o@FGg=2_1J^C(C}!O^PvOZq+?->IOyjrWaxy5yxe zhSEl>h8Wf|RBQ&v#!B1|;tTEYt4h5{{zhVBU zCpt(4l9Plb8c?#^YCO{p`q#eTOKk(rZE9`XU!Q9NeAKyKC}VgV^=GzIP;4&S*LISz zXyrSsPpdyJwQ2l=TU1y1v`c?Udwe~^O@=lt2U=zJkm&)O^Y|UNkwyv6oomM!UK{j* z*w^UWZ2R|oCrHDZ+RK~3F2KGYefIk6L;?xcMZA~Y(!+QQzDm`55{f(1U z#q=G@;AAi{0dATr8CFy;p)d(&jXBudHc~n|r`Nr&BFp*&w$^`FD!U-sLtd2I9|eVC zU=udg3pD?pQLTLw@K9qH?cM^*!(fs%9u+9v)TOx>W!+~TK5U%oG=C0MWEz%pCJa`K zIko3A1=*mu3S(u$(xG@LX-d8tl9-urd7GUSGSxJHwr733Q$I%1m~XNkPsH<}l%JJr~k?&|$YxmP}o$zC4dQ@d2D{il&+>)eF)A@A@9!Y+hz@_-Db(V zn^QbfB?=O5*?A*|eOsF4euH9S9uwE_6^E7rWScicYH@3#u@qCs-0>%V)2#hVDxBB8@%hvDRx=}GbQ@(`=rM@n>-?wLw9aDp-iY=?wmgIO5siZ$gaV?KiN{pT z#fln_hisz)yV#RZft^D&{zf3M#{n@$&pZs0i-xxX;x$h|@rAGjhNGK6ey5VLjCoc0 zIV`PqNR=}sEigJqA*?#dHcDb7@dT#@sp2WBH|ECj1?fEk)gya8Jp}_hXWTF%_him0 z3Ie}(bx({_W>L=XMbA;abSIPH<4VRx{-UGxU5Dq;m%M4dit0Y6QROT2(2gC)LPey4 zY$b<2MnDVMa7trk)+vW6D#nw4bLj7l-5Gte(*dI? zWz^})^nKG}ZdHv{thdC@RkpNGX^D5mXF~{=EsSkk5IOmoBoWpfey75xLlgL!EW{%_ zfO0*V{Cbew1pnDf&!=sxk<@;AZ9hZt#?R1Qw?DacL=V%r$Fr9KzLkV}7XRSQDTB%x zVwdxxIB!&{=G8du{+0YE(lUcmx8!H)WL57!*v$u=PV_(PIw7r^5ghAr@27wy5UMkX zX;*WahH=`(sM{Z3rlj${QVN_Ip|d6CX$efmK4wYCX*`*et*t&&q=q-v|a z0U|t!aA=;@Uq#9h@?lm6m`81H$i)!8(y#32_$ty&O=ElsE=S$V!Th$3ZGp z5thnf(K92@rbXT=fg8L>0e^rQ)(JVIKpFRMe<|r_$#Eb`6hT1*&`q&S$q34^!^yI+ zm@uWdlpRo+YI85WU%Jl~zPlkgj%PqcaHNQ;oxf{L^Vhw>(Sn944%`O-e?+NWu_bh& zhUmt~8>GKt=(Jg_CExj3qu@I;;tYqs1~h!A2*<@r$cImZpTi>pW{`#C&4Xmm4GiU3 z6gah7*F{^w#pEIHyM%tOmC5N8@>0;5;j*pw+#JUcvl`_s1>OBM1DsE{kGaOAKB{{Q@&&~fkP7D zC`~;!4&3Bs+HCqcLSLltqiTbTUVmxISWZ*$?|@z+Tk--13!Su%EjKbUC?OATqdCQj zbl~4Tlc--UKe!ixO*uCfDv>Dp+d$wMpU+6XSVf*!cJt8hW&cCto3c{vCN_O#2>VPw z<%N7^`%wHNKLgVd$uV5QzwD}XGX#?0U6Am}+Yj0=k4=2%+QklWy_$8DogMiToIHftqpCOy?N2VCIxn;qs+y;SWlUOEkQ=khkm?sdpSj8m-4GuGr_DLv0o@|hZX7zvmwP@MdnrNpV-D3&Ex2RA zdha_AGiJJ4rB9=KANPmfu$Sm|AU2hj3VV!_SuIIkK85$sKrgQh|8sTOye}WZpNL#~ zVLPoC1T;`+t{3~&d=@`|oR3GwXY%oRqY^=}_1Yg2K3@)3$B5bt#Z!{;*vhJ&Dfhso zAhyzi$3Mlcre3FsrwSsH430_yh7S_n_c8~*pM7Y3$L(Ss>zX_o)}W^d=$~MS()F5D z`+dM`n%qsoCnb{?WxsTo-bg|+?~>4&7siKB7v%gpEqP!aLVBEnHJ0dXzQ?G-CQE{e zE4wq~^cBAO7KkSqdGZy;YS0i9;9ZS;MCmUP(-Gj_$TBSsC%ALS29A(hyyA=DWzN*V zQIT^^5gHiA^_#e4*_iPjH`h#jyZ=Tr&l-DnJrJyvAOie6=AqM9Jg%+trKWAMeY^YeRrR4yAvA`#MTYa^KOP%6l3f+oqUp{ zq4%0pcl9_5C2vSX-bZOUs`&x(d;!sPyW@$ZwP3rcprHwOema7&dKo$7*t%rZyFeR$ zX@0VX)k*LxTv|jKO8pq4X99_B*+*9@a|3ZY3*L@M0D>>$zG*n2BxLJy1IeZF>Dros zY{ZrotWknt&ovtk7A~;?i{|AC+kk2;LzPnBCFJ+(gycee3(I9r2_y`cz+5)QuB>_b zsl*wHHc8XmY~NhxCC!$cD>5?t`b-4DPUy$R_Hz6sl}-AJ)#SR#tER1d|7^FJvw1s9 zjX%4RaH&=8Glx&IJmL8uuDxBCO9fY18Y;FSdBp{UM<1Nr5>~GlSf&s_^N#W5H`S=7 z6V9}_$}OnGQE;kKKF2$mj1V7;N|x)<5*oTbfOAMGVw_uf1;;W{(r-0J0u`bS!?-TULDnWna zip8WkLsLw*LCJceVQdfv4uK?%T!S($Uk3d>PdF0X$QB`i`XU_5iUgA19*{-XohW%e z|CQf*x1PFX?zE9}j>IM=LOjnv0dZ;F4ly<6VO=49# zlGBFKVaz)`Kb>$6oS9ZpWr!8lU(`9FYagJ)r6aQX_<#jkNYNYHF)a|P+&+}hG5t~I zvi6LG?{ZF!+VZ)E!{w}I%fK_9YSiJRz`6|M+7~6dW`}`ErJ&7EGfoeHbiy1T$5Tie zL9sp;Zb3=9=~_l1zGeS2g98~b1IDepbCTrmIvG=v!C=)-w0clUI;*HJpw7dtYxU!4 zfiFgcM%!$r<4+RQUzUax2CF=KB^Y>f*`mx;2`cXxPc!%T!6}tlM$uwA@!`O@hv3z3 zsc&QsvJpK&RveWX6@cy}ru_&FlX*lxY7&$;DxB%Yi!}@~n*(39DFw|ci|vSol!jTX zTELUHnQOUWxM-Nzu2X)BI0IZtMl+et`ao2T5knc$NO8CJh?q@d8bw>O($43zR&BpP;&j(m&MssdP!hYCJeo|CTmD0Xme@CDIRda zEdo9qaPx>OHA~ZU?6bKMmbVm_W?~Mu8rq#5Tju{Hm zmRUKd!0GAuT0V!$g&FSuC8=Xk6%%?ufX2 zOz}FEOjZ~*3!7E>EU}+?TEXANZOfupy=pVh>E%$&w~8RR4mpkYl4pi*ggk6E>BK6L z{q`+)v~H=45%NL#WRBB^(G%&l;6Jf{D>4a^-u2%@4k(U_nTHEo)Kvn4FfEnhh%6&@ zWwrfu)pI?U)D4f-Ug##`CZS}sZ)hBT+yhLoqrn@>hs8#`C454`I8hq#H{F12lcP3* zIDD%TyT1gHFc4z^`S5h(0Q0*u3GLicEiD4`1+O2DH4d4_E^^1jlSixlzaA5V3{H!k z=cgDe56T8?T?np1nLU|u*`($R=Vus*PS`nPo_$*napNBvbnjiqb&(OYvI@GXAOS|4 zR!lX+1s+4gl&LcKeQ}@I9pcd5uWw+rBvBS8;-DL36o%uBy`g+jt!TXjwwJP*NFg_B zYqLM1P*A8FY8;>T{=aEI&q+cFiJXXM%@rMQPSGlxjNap?-uvMk+fXSBy1lq&0|qg zI~+Xg7~UG45wNcUl`Kgjld%{Kz45~yHfeW2?$lC}B#Uf~clO&m;jAQzk*z|d(5Vu7 z=Uo}3^+|YT3a_b#uQFa=50B*Igh@8ZXx#2GfmeqE^d4jWULbboC6OkV%B?lfE>c~=i zf+3@e4dmqo%|HCvt(s7;7$r#HPcV;Cj#l$6Qkqt7>Wqej^A9q((Q6LUtz7IK+=Fh! zciVS#&A*%e*>7`Qb9jO7u3$XE$Q^EUk?X0vFm&6{#iEfWu9pp!JkzFL{APr`a8nj= zwM|)dM4zgE`p!dpG2r^ubg4=@+1(pW6H821IJqPaO8#n%8}x(*mWV0~xMarf3tCDI zlJdUKI4>C?)~8#0nie_4ZEX>l+r+eAQo00BO# zmjPTGU@)hw9OdR4>C$;Yg)|wDw3a!E!u@sDnupAfQ5({BgV4w}0ooohvS2H!Jb_N08^$g#%Apm^OYI_Ak0`<#~Un z!WH6>pZ6rEcgmN%aa{zQxfT_s@S*n|XVTLw&@y|4R+(JV}5oW~+vrr4f56w(GrrCNxOm zwkmxVF9?$9{B9>T5O)>Bmk`JOflWPMVxeQdX+~2~dy!nGUi*-G;TYv?78lrMLSp2%b7O<)s3GUGPBqRRBesQ?~dv2%)|pvbZ7N&xn5P+cc{{TiA~R9agJb3Vl&e(J#1YG z6w(Z%CWr(wVazXp#I_UEnpi;Ade`95ZqFHM#SyU%Ot(Qvw;q^SS$4ITsF7(%J5Q7yk;lTWHpv+dXK zKDfr{G-XC8rAhurIm}rA z4|hiO+nOub9PFlq7^A6qbdI>Z92ffGV8O$|^YCLT!}pV%ITjYI@au3nK4r5)n4!-g zN~9|*xb9?bLk_H6*Hld@UT8USiSFb}l271=ccvM1RzjCGJ@@<98Jma?;dagYj|U&- zj(!>{f7kuiI3V~!84We%)C26)s?}y72AXsAM9+7WaYbSQ2sg)%@Yb|&tcUr~`#L^I z`6g!9T20$U@lPVW!Svk+AfVM3baxT-<8%J*2Miwh5cBBK$i3Z+*g0kvs8NE$lFJaK zb!Nj?YlAqQrwd!U&mARx&s(&hC>u4hy{-jS(Ojn*f10Wh=3}cHz)UzZYY}e*CM9O_ zq2pS|?nOhGX4&*)H>VwyI&u_#bZ2NbJ*N6)GX2=~+h1(73$8PpV3M`JS$m`) zI_qE%PN?UM1LdO06>KDrY$M{QW!a;S$;a38#_IFQhMsXCGtThd^BGNXuKf>t?-ddI zpT{zeGrx}(ZrolUIzJ-qeqr}|h->8JskJ{d$lItX^SQQW)6uM5V!Z>;n>o2uA^D+s zjP(#x+TusXwU~+QIF@R)rWr52>aCR~_4h<=XhN;yI=;9R^|}MF)C(ugd68G{-lM{n z5p1<>DBPR~Wi6vgH~0EB9wo$Uu$y(CYz%z|BI)s;w^<+?%2UvBpEQ{gt#4`j45N$< zI)CA3yrDD`BfAa{S01@zBec897_?0KGl`zx4WGCh$sYem3M&{l{2RfnN>!9d`OS_= z9TW_vl(**aurjyIbM6WqamR~s;l&A$OmMMJTk*S)rxkpgNx`tXkX{+@R(asDGMOmN z3LAL|m5+$z<<&B!DpZJEd*5Ln?r>uEzK%HT`T!mKXU`2KxbtVmyUeLPA$xQ+9>lZC2oCw8tln1%Jo= z@P1{Y_vSR5zGytyxQEkBU|DREu5q6Eo!xxkLz*G8F>^N3utq1aN6SFA?abAp8M@57Rn)ESf>c&619*D5fqC*+g*Oy>j(ic)+Ekw-Pie!sLX z;ClQAGfQ9dUU5}GvK<0{)e4v*1?Hes`cW?@jR!kvj=f+b~qh%fn$NU}e;XZ^6 zKsfu-pF7zeBpA*9l8rcFe4V!E4FHE+fXpy;a$z(xo8ge-d`)mY2%=cd4OgO4ZJKqP z#wdCgUb*vCPwSC8;v@4wx%8V5flD`K1pJMh(?jS+g*SwoGjlz)Rlxc)J^&RD<*FNy zx}fNGBZB({Y7&RMND3)6)K02;4c6|-jJTu~y4ERz%#Jxr{r&?eY*bhRj!x|9)?}-3 zjBE@I@5_20XhZ5WQxco`l}@JqWO&8-$~$RVBt-Tw+LQfk8^H*V{0z9so`QWDM~hL) zY#fq;E!t-1*Y}a~9xRDWu?^sC)1fF#p~0%o$`NSnU~*k*xo-y=>GXK<07o(mFx zj3(cSPQq%%CB*y6nz=7XU2(HAh7Jy2rLWbGI6^|Km|tE0YHgM5 zp!gJwh9G#bI2;u4Q(xgGb|2O8R*mR&%5YP;)hOisx`Okcw9g=zLWPcM~8Ka9dZ# z(`?Tnoo1>%%ibnH>=y^W#s6I~L5CtzndT_^c3OM)r?w>lYHOis@eTlcA;|#C(UyJz z^SJOWrM2m|wcar7paG$RM_@MP{cDFGT3XSr;piAx^To1O5BVlCmZSU-fJKaSEYmcG z`!TTTr#T476J6jw5rVV53KdjCQ`OoUrVCmeXF2%bT6G{2U z*Pp%-bzgKIE*QII-ZX8u0BNL2xXt~!7)Fm9V-0X7X=%DoI&Phq>vSjtmPV%<0Hh$B z9OWodWmX%N$gWNJsADjZ)t!akn2N>!Y?nATDl5Z^Xa=b1E}0+ri3C`OFL|jXkx9m# zqo8VrOd7XS_Mzi277XM{^>GPj8XRT5L};S^3NOIV9$5r^FKp<0P{RT+{>aRA*a)JG zV~+(N({gIQv0g2&$F?$}cX*FuX1b!JQw+;u9{K~3zqXda%5>y4<0CbYq>K*drAdp! z2n`SBZuYLt3H_Ksd_bXo=|U&5_wdKan%l5$rva=%~Lf5Y`v# z-OGlS+WF!t4C^YAw9x=KTc}<`qmNUUiECj$}&m~l_I$-3uD}RbiH+RWSp^oZVseTx)12Vk)!( zyQfQ!t*9-c!0LT8IVPU3daelH&9|Vx8Ncr2sT}MHRi@r4(aZ7&^qv5~gZjfDUW28# z-qMRO431`ceC3dO!jUL`hFqO@>g^i{0;9vrEo#PSE8B$vmOzRyGZ}i<0{??{A>I`_ zCI67%gn&B_Cv4vZI!K2MHvKZ z8h`d(fJ3D{TjkT=H0*mnw?^%OF2$coL#HR$-#$Fc-yL;U(VoVa6)1_GoyfG%mTj`B z+AG&*e_MOm1I5K-R_%y)=IxIq`y;xQYeA>=9f+Sf!g5@6opSlAWCS4>mj&tCv^Y#d z!b-3c0A>7<(sxYbHd|CKluD{sB8nYPmi{h}%uHF7jT~dQ`s^^N!{ft#zOg?f;L@v! zT#Kd1*Vpq1c`CgpWz7y>Y@d@1IufW%#ALv9#wyYg%rx)1J+J-2$xgy(^ z_Q6+D+%z%?YZMT{qr><*9$Cy>4SmRWAWIXTu$SjS!E0S2&or?-48nYWQ{+H&QbQYd zQsO_6&lkg|9CSYWEJ{yPbZ`tQ2;d&7H$9 z3>+m(!-k4k;d=3xS-zVcXtTKJ+I#=8@7|rMGDYb*9=K-#xZENTbS zYs)Xh9$;pDColY$Am0HQ#x%=oP2U3|<1!^+nK~x1NNC$H`2?7#Czwu>FIL7#~T18PLRq`)wfg0`zglH{Hd^>Vx8-s`VprOM&{uF#_Nm8Qif2k39!8~}$xp0F`mNjshf0-oXg>4f_` zkCdS$Rgn|)5Z*zbs0Utkg=bhVX+jhs>rTvKQ2tQKO8_n2WN(!I!h$74TU$Rw)hr-D(Ul+rYvfRBQSrv&>aXHo_NmIYwme`+9GvPbLoodj&NCd0=C9CBOi12N>sOu?{^6RZ zMA3U$dz#TFZ?W)M1jiU;Z686$ggJI>&l?9J0-qHVs4Vpy*7#efysK_^BI;%(>}9vP z><;Vn*Ie(GLZnZ+ydy8SBSI0pVZ3sdfP>dca`pN2-u9&%HS54|>+uNHcu=&LG(D}R z`2zn*!#QT7RSOWCI?Z^s8IO5Z@EDHf9vxoU?65?yyT$2ELcHEP$29)F7`w#pP#|Ok zKiZy=<~MvPQJEHre5|x~HBzRuk}SkVf%|E=Ul9BD10HJOQ6{)OG&dC{ zuwWYPLln|FG-b`I;~Balu|t&Dq6wlznao$k5jGv^v(#)^%AG2 ze9$G0)TJ;Ut}(1DSvb|8Y5K*JC(~~9y!sRl2e#jFrZLAc%dwgt0JS%PtS3sBD%(^x zSYmR0jN|E=AE${osjC@`AUC$Th8$OF8Vfq?pQ!@pS)W`l?sQ!rTqnEjMSQzrq+Kjx zgiKX^m5;PYlAJ5wo96B5d@3^v_mWnyZKOvqH0oBl&Yny*_MHwlj>L0Js4~3V@SYvJ z5DYxcSuxO}0EJOx&5bibyAnqVJsS1Q&mB@|JK6ml&gAE>C}ej%oJ+}A4mKeJz^=!a z=b|&JoFCGGHFdabMf{m-?Xs*feDAtew$=ee2Mq1u-tq^oW`952!A%pe%Q>u!ivny{ z)hJm<^LDfNNW$B6&YISLkdm~d{YJ;8F_rIWe0%fI(NCB2)%wS^YLlYYzOi!yHsMKr zN$b8xMETZM)yIUvP)T@~$;oVO;3Iq*k*Vo#;$PVEOE1_8{Iu5x2jOp{qz@qSl(S*a zKfSNX`0&Tl>jdU^C9YTSmD)ld@*`E-B>treJ?rQrGo6Oagv{68BlJ<3#m*VhJ_p)= z;u~KEUTs*}F$TFBv!^PD*S9;iEDRmUk*wTo3Qw#LMy9Gix8fp}eY`c1^KFWTIkcBE zGKS2oSq$6q)i^{-^@l|1W&bO*Rq~u)y6Sx;4{<;>KM35O9PMTp8WLY^cNbjKp{G3{ zCg+u-^mGw(XCLy6SW5VBUN{)bo zJ5zH4^`9}P2Ra5RqWYc=liv+P$8u-SIfY8SF=Uoq`U$MCm^zJoW7ljpvfD<)pPx&e zj4%qlPum!~)j!z`JdKeq!eJ^v`n|WK@1er(q6;M&q$!PXn_!zZIv#WykWr&dg^G_& zSE+n}5Y7;+u{lsrYW5%>#J(xfF>YrK%H4TT6&0_N@O1Op3)hGTFd9jCsZQ=d7z`Rm zptBiYDQ)-tI__p@$0#^l3PxmG_X9&#YYean|HmV+15T^o(4wJHAKm{i02{Py96~nl zG(E>*ye>CBk)?TZ9GH=|5^&Bt%el_^>P#jmyu= z=kHd~t@U%7s0KTITcI3uRG2()5Hydske?%t^sojvcJ$ow(WkA;F(4y^9V+W2TpO;` zZZBc>?WK2fzzI61x?T+u(#bKB;ew=L>MQQwnW?x)2|iIgm0#Z1lt=r@PPvid`G|ps~S7At$#$X>j2b@9WZRO7+D;ser zw+dgzv*J(=m$3`KoE~T0WI?^MvLw@#2joJnLplxT1GMuFXjuV59<_b~wn zH+~~p@GTxhT~f-A3klwH7ZG@xsfBf#=S8;JFa%8fHXh(&cfn4sW8l)hd@i3KYmMv5 z)~kguCpO7QPs_=;BXsi#LeG}_I0xnjfFtD2VY9ZWQADB0Xn*Ol4Ee*5b7o|QmE@?2 zi8UnT&22T`7Y`Gmy7eiL#+*`!u&o=*gOP|B{Ko&Une+aq@{jwtjGW9vS;@>MJK2tL z>@rRnM^-v^2_Z8wvt_Sigp@raQQ4a)BP8S4iGvVve=dC=_x&&2KVFZE>s;qt*XQ$o zy~pcmJ5c?dzKJpTVw5M%`L)SI<;7WJ#p3`kX#9U>FUNZ|J*?N02h1;hNYH8;^<>7R z(JR)Z)S8{st`SMdFW`=?97m6-WJLJLrtPC2>_uH+Vwgj$Bn&s%q|j^BQ80 z6Xib-tzuj2>A9-jQE>!t2?Zh{#{u_QDxmDL}EOo{Zf zW^)L3p@EPE#nC8kPTs%!HUC$rn1bc@!=)d@;;Y4$K5-%k z$#@}aCR+KU-C5f7fb3in11I5}+%<}C`R$J@UPqv})ZV|HcGF3daW5*XvajdXlSZFT zoJ>Zb=BkZKtyC{(ywEXNl_?0NaaD!OMliS)q_#dJNZYR#wjuwzY(X9g(s5v9@j3!7Qk+;g1d1J)FqX$s$%8^b6mb?FJ={+z*hi zQe?4--F#2#@nCf~pLwT(eTPKxv;a-<+b>$Un;E+ljN~#PbEjnO*UkPY*!OO$p@|f{ za+|N$W0Nm&M;qQJ$WC4s+SGBHEm03;r}S(^%4!kE;J<7%TepR!8`~N<%;?rw&*WSk zwggS*T?Rb2o^cc3=*5qNaVkSjeX`W|uS&2n;y9GJpUO8s;RhMU3)*8j|C!iIZNk1^bsIxW`grd*+%LSK|+)X+8U$7|pHE_4d(#Jf%pI+Ye6IL6A zD8B#<%%mde7AuZ7rW-}}J(YoXkR#W~!?#M3EhCI*wi;pI<8)kj$f!7dq>ZxQ)2*Y` zXbNBwK|2&CJEPieWsZCsXbS&z3%C;*=!0`nuWr@d>3|bWS+D4LqKb5@n*9#WORZDF z(YeqV(*v7N`^6>5MbyvD(@_nAP+^j^LQbBCx_Z0Pg9`h11z}BVF9J$9YZ&Afss&g$ zd&Ga$;Wb!INayEamq>!9e- zl-g&NeoT!xKfB?%F(GO{%zlIzin;OK9upD>M;G=#oO`M8X9P_v5sq9^^5^{683I4j z3&3BL=n3@r7MWm2oIlS@&*vZFG@_M%;NWaecLb~icFaQfW3J~VdV!ap3`L zb_+%bUr<}A)L8?0`;`_@J`4T)QX|vwo}6R4#z7!efV~GkT|a~Yd+zf=$8t>(Xg2z$ z-96mpZK~rmqz4I(Arhetmw+pLXb?8MSxn2MaAi+2`QeD<0T0Y_W5H};*D+pb$0B>E zV9}T*4m3bzL!l2WyHo#h!r@i+##Ln~TVRE0tB?`V2I{g?+?-ofLdnvRk_FOl-)PyU zJi5$0o2Pyt^;Sm;hFIx7*j^Zz*37Yq zW@V#OV+^!r(XGvLge*vQkZv%fU`Z^KY!ExZkMykjeO#>?+q(u}ktDqYJY@tUB{hB-| zaEp5u?47cmG0blXZZ$|e*bgM4TJad~8wivOSKIvaYk80ZAwqqT!$wc2Uv76;6{VL| zMeUOfysVzN=RVz!^bEBN3yZqU3_7(AR+%2@KSsDdp}mtG>c>;rwJQhR+B@fWzRsEB!!! zbqMMYjj8TRL(27_7EMAsb zrv7`%MG3XlzdMpN$K^UkPIT@C)^@|X_nE$^nAY4ldFB`M0&-&+0Q$gq4e z7g~Sbe&G6eElFg1PLX&;Im7(Z`RBaOi0svc4ckCI^N6zS-3xkm;a8glh+lZP5=TA* zA=|+5P2gdYTQWZkWmMA)Z@t%R%>qX~{_yvwOE@Uh4E|R41<`?$vLlC1GBO7XYu%su zu#^33!p_~`cvoGTmxH!f{Yk`j)9l~!jv=GXAF5eEk zrO|8lGgQ1e*}RnD)CzgNn}qr^Z&bYXLWz`u_Mn^budhgHlEX(orEePIJmi~e#UdEa@@j~>4Net#ho|0dcONf++JqskEwTO z?igO;U*fHow$5%F|8U}g%6Go`GSr99)Rsfch%is6d3QuAIYkV{KYyZjvpHB=DcTId zh)xxE{K7YN5`q`wPQ@mA2H28`6Ysd3{&tz$Z2DDC$RS2B} zBc4aPG;Wop`n0B9upx9z*YO_TnRW2OtGcKx&?ki>12--W7dGvs#)Hs}b%B+sEwGY4yvy@v?KZt>Go5G)glQk{%B4Ai9-i zx#n`;o>Lx4%qMjNWP+(YE>db%=Vo|k2xy+=gUcB1UKV;PyD3BrN@`Kc zx{Z0<6`GDm2iQCf_z6Lq0NzTNu#pE&@5sS<;XJzsOIx6CO~AsV|2=ypXqpx_Wi}0R z<$AaDbupXHi8UO^!oEr^1xp?Xw*y?({Q_}N5*PJY`|@#&c0;0on&B&F*5V^?&Hor{ z9Pv|PaGG}D6?u@k#^TyeV_TFV@9k#wfkW}5#N!ox!7fm#VZ0^xv$nE})psJxxBf&u zLe+3aHK8eo|%{+!KY_m_6Mvs1PbolKQb>k`wvJ>0!O2yUH!-J zV7#c`*<&$5BV!6oR@pI6Zif%-Pe{r0z%(O<^6<)6=T+Xk%@Wl(b^S+Gv%efP9UG)i zDE0E?@~0{QbC~jCSV?9VEN{sFxzu~!(=T%lXdzKA9~S3MhB*I9lmHvgy@eL;6qDAL zxsU1rE-i;eq1$Z#exaZ-nr@b$VRyvgPC_^JLb+lG|ooh zqUzOPASVpig2JE{#glGjSJJ%MhBQJ*WTfeishzUSBdHLK9&(yE%6v2SR^vJ%y&JWAiQq;DXNHk3*Ed+P1ay(>vqG0pcG&SPfbUJCDvz zKb8SU<|hPCw$I)KO=yuDF3M+}z4+#_;3!pzz{2nBPW}xE$N)SMxHbX+xQIt^)PW4M zXXLU1utd0!gKcL>z+oIXdVEOqqvMQk`2X)C5f48+$JQbS!WO31a*h{yGxsc{pFTaB zn%&66{|WP(BOAUS#?OnnYo8aIL17^8qM)EK_v`7Y?-?=gegE6|1@Y9x>Q!^0(VA~{ z#MRxLQ6VCLadA!ld&hP!(VuXoV-CMHR1O)$E4z7^w3-*=G!MLN7r;V%;&36v=cv!} zgch7rGLe6X zYo#rJ)~tb|O+fJvfe|Qs6EK~wV=6^QHrfS-@0p87#rsBdL|81q_})lrjjqRp2QlG- zjh=e%*u6sMqzlY~p6)&--K1=O!*YRkRNM!7rL{|ynWvynTWeuFxt54GNR_Yn!mDCF z;&W}3@f;>kH!1OtTCY*6QSr(vq2}&Tfn?)BgT~_Od7UK68Jsxf{SMsdb&;zgm~^`g z1EcfiVv}?;z!p^E5hkf$npD$I=zvJVh}fmCRhGa)=(E$5T7=y~BTsEeE~myEO-KFh z*pJfpOb&W4@XOWvO+_g3N;wP{y~CJ_!X=m~T$$hU{t&w^B&_Gha`e0pyI<{rI(Zj{ zaO6)!KSaJhHo{xfr&d@@IyH{F3A(Y23+gHC%cs=IR(w6}ai7Fa%8#jD_Fjt=gDd2M zeap6C?M+E5*d-Z&(JphVP{h9nz2rDoc+Q@8KB8}@#PY9zXv|)2Fx6T%qp1I%-AAAr z7>Z;tmxRXNAP!xnxTX8Rbt%n8;P(u_DK*Kpk1>|v?^}pddZ>*7%r5em>M`QXhhYC? z#Ck6=Wg2u*aId*;PB)(O>gPDiBsjZk#0{Cm3~r{0e$sXOaQgRGTbh_n>0SAnFU;#c z^mq`16qlDk4!oAy;t6;2W$ni?M?&Il0tH7^(*|y z-g^i!_PbZroR1=Dmdtfmet+6sVfJv)%g1&w%K=mT-dUmPXT6yo^1|NjBK+?xK+#+Z zI5}M2zYE@Wh2)?;ED@D-?kjPInQ6gDseKG2bbTPAoLpxti_@NwL9PPuiQJ<$fp%;K zaPhcgn~LV&YY>RYSD<}F;^DNkpva97Qwwr4j(9A{f_kbovW>d)!{5oQ79{!z5Pw6N z5{;lZ)Jnz~ES}_nyyZjXB{x#A-b;_wRx#QLl| zw4%L*0p9{^c|6ackSvyqRi%4-7_ytTDc(A;`X1`i*6!Q*WmZ zBa7Qjyui)y_}li#2+vUIM+=XyciPZSWnVMZaNIRxf+!`8*egr|k>f>ygXt2ykVjdH zG#fa#fR!Eh64?u(YCfZjcB?(VIq#5g3Z&GR}Ej*qjd&ZB^mlQOFGumK6ddj}hKN=~CsUb`ey zjxuZ-4tie`0iX}8&l-QGMGJ7aOc83?-ZcPzP#7jmh=ZopzP?~hx&pU8=9i2`?7HFg zt2MPm&>eTJu3__yjX)nk zQED^x1OQmsNaK)HJT#2~Fp`m!u{N8zC!%fHV5p>i+X8ci z9U@JRu?GtAvnf>58JTe0-q>g?w@Ki-$T87Lv@->~!_MApqE-kKyKD~?%!WW|^Y3rA z`E4;i<}Gu?2&8!8$?mt=s{^dnVf z-R1O9O~D29SYUB9DX#Q3HoH8nvn2IWVe1B+g^?{sp7Y}h&A4ZH-M+{8#U6F|8*7KYGK31`h zF+Tt0XNGKww+7=x%$2{zRD710XW{7&?Y$*Nqb;}<;M0N^6*tmD<>8&a`VAXiou%(M z$<%tJB$HC{Ghnl}O*!s0ZW)+Af5JUw5UyqZ+xZ4+zbsywYsw`MRY!I|#?YkqWw`hscuP89XPNJV&nkjRFsnK<` z^%C{BnUy&&zS6Yo4c6!BRc&nOG(WwA%}+5uH1c;{ikK;k=944kd@Att`%sbw?Xo#8 zI%m?yqjU=?jVed)@!aUuFWErynY~@CT89;w35@lvo~gJon4i!(v3$UPQqv8>%MAML z8J@|wct{+kt8QF4(~|nPKsCA7w8BE*?2pAYKu+x*q3|z(=zR{PqRjeN9iaSxI9vky z?>;uAp?y**){vTJ_FZw11fZUKgaKXLZ9y+oY7b$`Al-ALusK*JNBQj#i@aw zO4i<5;TgD^-5uP3c=*J{e_2-+_>80)i{{(^bS|V-m*BqtRPTR=r;^lvh~9p113g#n h>77BZgWNc`o-VKVis}`~heAkoE#xh=auus*{{!+0xZeN( literal 0 HcmV?d00001 diff --git a/docs/watershed.md b/docs/watershed.md index 8287966be..060bc260e 100644 --- a/docs/watershed.md +++ b/docs/watershed.md @@ -41,6 +41,7 @@ analysis_image = pcv.watershed_segmentation(rgb_img=crop_img, mask=bin_mask, dis **Watershed Segmentation** +![Screenshot](img/documentation_images/watershed/watershed-labels.png) ![Screenshot](img/documentation_images/watershed/watershed.jpg) **Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/main/plantcv/plantcv/watershed.py) diff --git a/plantcv/plantcv/watershed.py b/plantcv/plantcv/watershed.py index 3c380a6b2..fb3f0f963 100755 --- a/plantcv/plantcv/watershed.py +++ b/plantcv/plantcv/watershed.py @@ -68,9 +68,11 @@ def watershed_segmentation(rgb_img, mask, distance=10, label=None): # Reset debug mode params.debug = debug - _debug(visual=dist_transform, - filename=os.path.join(params.debug_outdir, str(params.device) + '_watershed_dist_img.png'), - cmap='gray') + #_debug(visual=dist_transform, + # filename=os.path.join(params.debug_outdir, str(params.device) + '_watershed_dist_img.png'), + # cmap='gray') + _debug(visual=labels, + filename=os.path.join(params.debug_outdir, str(params.device) + '_watershed__labels_img.png')) _debug(visual=joined, filename=os.path.join(params.debug_outdir, str(params.device) + '_watershed_img.png')) @@ -79,6 +81,6 @@ def watershed_segmentation(rgb_img, mask, distance=10, label=None): value=estimated_object_count, label='none') # Store images - outputs.images.append([dist_transform, joined]) + outputs.images.append([labels, joined]) - return joined + return labels From 6e5b008e662e59881572be329708918676a7fadf Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Tue, 13 Apr 2021 17:20:19 -0500 Subject: [PATCH 010/178] add detect_discs function and update init --- plantcv/plantcv/__init__.py | 2 ++ plantcv/plantcv/detect_discs.py | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 plantcv/plantcv/detect_discs.py diff --git a/plantcv/plantcv/__init__.py b/plantcv/plantcv/__init__.py index 637fa9284..dce46cbe2 100644 --- a/plantcv/plantcv/__init__.py +++ b/plantcv/plantcv/__init__.py @@ -74,6 +74,7 @@ from plantcv.plantcv.create_labels import create_labels from plantcv.plantcv.floodfill import floodfill from plantcv.plantcv import analyze +from plantcv.plantcv.detect_discs import detect_discs # add new functions to end of lists # Auto versioning @@ -151,4 +152,5 @@ "create_labels", "analyze", "floodfill" + "detect_discs" ] diff --git a/plantcv/plantcv/detect_discs.py b/plantcv/plantcv/detect_discs.py new file mode 100644 index 000000000..f5aac1383 --- /dev/null +++ b/plantcv/plantcv/detect_discs.py @@ -0,0 +1,46 @@ +import os +import numpy as np +from skimage.measure import label, regionprops +from plantcv.plantcv import params +from plantcv.plantcv._debug import _debug + +def detect_discs(bin_img, ecc_thresh=0): + """ Detect disc-shaped regions in a binary image based on eccentricity. + A value of eccentricity between 0 and 1 correspond to an ellipse. + The closer the value to 0 the closer the shape is to a circle. + + Inputs: + bin_img = Binary image containing the connected regions to consider + ecc_thresh = Eccentricity threshold below which a region is kept + + + Returns: + discs_mask = Binary image that contains only the detected discs + discs_coor = List of coordinates (as row,column) of the centroids of the + detected discs + + :param bin_img: numpy.ndarray + :param ecc_thresh: float + :return discs_mask: numpy.ndarray + :return discs_coor: list + """ + + # label connected regions + labeled_img = label(bin_img) + # measure regions + obj_measures = regionprops(labeled_img) + + # Check the eccentricity of each region. + # A value closer to 0 keeps only the most circular objects + discs_mask = np.zeros(labeled_img.shape, dtype=np.int32) + # Store the list of coordinates (row,col) for the objects that pass the + discs_coor = [] + for i,obj in enumerate(obj_measures): + if obj.eccentricity < ecc_thresh: + discs_coor.append(obj.centroid) + discs_mask = discs_mask + (labeled_img == i+1) + + _debug(visual=255*discs_mask, filename=os.path.join(params.debug_outdir, + str(params.device) + "_discs_mask.png")) + + return discs_mask, discs_coor From 3857e9ae637abc20a1b7e74ef02becdffb675d4e Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Tue, 13 Apr 2021 18:30:42 -0500 Subject: [PATCH 011/178] add test for detect_discs --- tests/testdata/discs_binary.png | Bin 0 -> 1363 bytes tests/tests.py | 6439 +++++++++++++++++++++++++++++++ 2 files changed, 6439 insertions(+) create mode 100644 tests/testdata/discs_binary.png diff --git a/tests/testdata/discs_binary.png b/tests/testdata/discs_binary.png new file mode 100644 index 0000000000000000000000000000000000000000..ecabbfdf2145675c0d1852e0b9d236d849c45dd9 GIT binary patch literal 1363 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4xj)-jM&AM3=FJNo-U3d6^w5WGWKbk3$Y$N z^IQIxZi0k!#ozW=p_@!s*KuT~UOKg8s;BnkFtxQlQ8Tv~4S;|dSN&x7NwJ6QPkcTh z_b~oRW=i)5_D=?z9DlUhbgkjuKS_OJ>BI6*3Y&C%PBfobWuSUqarL1oHtjlG;!g}y z0(U6%D|R0et>lUn);k$|Vpf6YJf~|d-k(G^sl`lMKEd?iB%9VXoYS9Zrugnq@mI_~ zq>7?ctd;wd#3qfH3CAadCi=-ahqY=}@kPy=lYG~ zzmmW5?w0yV$0ybv;;&Q!1=*9?E&rA0tK3)m<7DT$&w0kg{t5a5dMDXWe01u(PcGBwhv4@r_a&Z~om+ph^vRuvq@UOot@$9fvv-cx9?y45?OgejXP=mPh-qiL zjHb@S=o6iXly)}H(O5G{`b6U)qn(X&)N>SmpR7E@w`s17dXD1llbMIqHqD-+mZP*A z$kf|3Q${UEdG^V~LuQ+1&QZ-#sXiHb$ZgZ~IjV0|qE7}M3fnYQMm0wjjTyIT@*I^M zwd;p^c#0-%m>I)uY;?l&P+F1hnG*`?oU>1EHBfG*qJk+Krh}~jIwwdVEoH*umbFV1 zfL6J4MteHWnCN&;sBBUb$V#C5MHAiFEWK}dXo1`|V?%#Pi=^ZegG6afPM{lsz6=rq z8gRMAcZnL%faRRCJ$!)%^a*d9un1@XD1=-=2Ke7_SOqd*-iF4IR-ge6i8H5wXqUu^ zS3tB!VlR~L+yin+Iv4j7u+1SAv-hZgOyBiRs7^@ zkl9;42vq{3-H}iH)e~8erLh{5zJs*XEOfXBQkgHHcj7ltr*^|D)+hnV2%XI z=nL1K5CdxYc^(w<^>IMCf1g1r4H=)geaN2ZjPrrsIP% zA8`WbIqD~02D!)iQ2dkblgC+}tfqUeU&(dfB~Kyr;p`K+T v*n&j&b>0C+lleqxkI)}#Ma(oUhHS^>bP0l+XkK0{2(W literal 0 HcmV?d00001 diff --git a/tests/tests.py b/tests/tests.py index e69de29bb..eaae0b720 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -0,0 +1,6439 @@ +#!/usr/bin/env python + +import pytest +import os +import shutil +import json +import numpy as np +import cv2 +import sys +import pandas as pd +from plotnine import ggplot +from plantcv import plantcv as pcv +import plantcv.learn +import plantcv.parallel +import plantcv.utils +# Import matplotlib and use a null Template to block plotting to screen +# This will let us test debug = "plot" +import matplotlib +import dask +from dask.distributed import Client + +PARALLEL_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parallel_data") +TEST_TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".cache") +TEST_IMG_DIR = "images" +TEST_IMG_DIR2 = "images_w_date" +TEST_SNAPSHOT_DIR = "snapshots" +TEST_PIPELINE = os.path.join(PARALLEL_TEST_DATA, "plantcv-script.py") +META_FIELDS = {"imgtype": 0, "camera": 1, "frame": 2, "zoom": 3, "lifter": 4, "gain": 5, "exposure": 6, "id": 7} +VALID_META = { + # Camera settings + "camera": { + "label": "camera identifier", + "datatype": "", + "value": "none" + }, + "imgtype": { + "label": "image type", + "datatype": "", + "value": "none" + }, + "zoom": { + "label": "camera zoom setting", + "datatype": "", + "value": "none" + }, + "exposure": { + "label": "camera exposure setting", + "datatype": "", + "value": "none" + }, + "gain": { + "label": "camera gain setting", + "datatype": "", + "value": "none" + }, + "frame": { + "label": "image series frame identifier", + "datatype": "", + "value": "none" + }, + "lifter": { + "label": "imaging platform height setting", + "datatype": "", + "value": "none" + }, + # Date-Time + "timestamp": { + "label": "datetime of image", + "datatype": "", + "value": None + }, + # Sample attributes + "id": { + "label": "image identifier", + "datatype": "", + "value": "none" + }, + "plantbarcode": { + "label": "plant barcode identifier", + "datatype": "", + "value": "none" + }, + "treatment": { + "label": "treatment identifier", + "datatype": "", + "value": "none" + }, + "cartag": { + "label": "plant carrier identifier", + "datatype": "", + "value": "none" + }, + # Experiment attributes + "measurementlabel": { + "label": "experiment identifier", + "datatype": "", + "value": "none" + }, + # Other + "other": { + "label": "other identifier", + "datatype": "", + "value": "none" + } +} + +METADATA_COPROCESS = { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117770', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none', + 'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg' + }, + 'NIR_SV_0_z1_h1_g0_e65_117779.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'), + 'camera': 'SV', + 'imgtype': 'NIR', + 'zoom': 'z1', + 'exposure': 'e65', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117779', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none' + } +} +METADATA_VIS_ONLY = { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117770', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none' + } +} +METADATA_NIR_ONLY = { + 'NIR_SV_0_z1_h1_g0_e65_117779.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'), + 'camera': 'SV', + 'imgtype': 'NIR', + 'zoom': 'z1', + 'exposure': 'e65', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117779', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none' + } +} +# Set the temp directory for dask +dask.config.set(temporary_directory=TEST_TMPDIR) + + +# ########################## +# Tests setup function +# ########################## +def setup_function(): + if not os.path.exists(TEST_TMPDIR): + os.mkdir(TEST_TMPDIR) + + +# ############################## +# Tests for the parallel subpackage +# ############################## +def test_plantcv_parallel_workflowconfig_save_config_file(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_save_config_file") + os.mkdir(cache_dir) + # Define output path/filename + template_file = os.path.join(cache_dir, "config.json") + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # Save template file + config.save_config(config_file=template_file) + + assert os.path.exists(template_file) + + +def test_plantcv_parallel_workflowconfig_import_config_file(): + # Define input path/filename + config_file = os.path.join(PARALLEL_TEST_DATA, "workflow_config_template.json") + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # import config file + config.import_config(config_file=config_file) + + assert config.cluster == "LocalCluster" + + +def test_plantcv_parallel_workflowconfig_validate_config(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_validate_config") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # Set valid values in config + config.input_dir = os.path.join(PARALLEL_TEST_DATA, "images") + config.json = os.path.join(cache_dir, "valid_config.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.img_outdir = cache_dir + # Validate config + assert config.validate_config() + + +def test_plantcv_parallel_workflowconfig_invalid_startdate(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_startdate") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # Set valid values in config + config.input_dir = os.path.join(PARALLEL_TEST_DATA, "images") + config.json = os.path.join(cache_dir, "valid_config.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.img_outdir = cache_dir + config.start_date = "2020-05-10" + # Validate config + assert not config.validate_config() + + +def test_plantcv_parallel_workflowconfig_invalid_enddate(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_enddate") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # Set valid values in config + config.input_dir = os.path.join(PARALLEL_TEST_DATA, "images") + config.json = os.path.join(cache_dir, "valid_config.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.img_outdir = cache_dir + config.end_date = "2020-05-10" + config.timestampformat = "%Y%m%d" + # Validate config + assert not config.validate_config() + + +def test_plantcv_parallel_workflowconfig_invalid_metadata_terms(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_metadata_terms") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # Set invalid values in config + # input_dir and json are not defined by default, but are required + # Set an incorrect metadata term + config.filename_metadata.append("invalid") + # Validate config + assert not config.validate_config() + + +def test_plantcv_parallel_workflowconfig_invalid_filename_metadata(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_filename_metadata") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # Set invalid values in config + # input_dir and json are not defined by default, but are required + # Do not set required filename_metadata + # Validate config + assert not config.validate_config() + + +def test_plantcv_parallel_workflowconfig_invalid_cluster(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_workflowconfig_invalid_cluster") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + # Set invalid values in config + # input_dir and json are not defined by default, but are required + # Set invalid cluster type + config.cluster = "MyCluster" + # Validate config + assert not config.validate_config() + + +def test_plantcv_parallel_metadata_parser_snapshots(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_snapshots", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS", "camera": "SV"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + + meta = plantcv.parallel.metadata_parser(config=config) + assert meta == METADATA_VIS_ONLY + + +def test_plantcv_parallel_metadata_parser_snapshots_coimg(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_snapshots_coimg", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.coprocess = "FAKE" + + meta = plantcv.parallel.metadata_parser(config=config) + assert meta == METADATA_VIS_ONLY + + +def test_plantcv_parallel_metadata_parser_images(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS"} + config.start_date = "2014" + config.end_date = "2014" + config.timestampformat = '%Y' # no date in filename so check date range and date_format are ignored + config.imgformat = "jpg" + + meta = plantcv.parallel.metadata_parser(config=config) + expected = { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'images', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': None, + 'id': '117770', + 'plantbarcode': 'none', + 'treatment': 'none', + 'cartag': 'none', + 'measurementlabel': 'none', + 'other': 'none'} + } + assert meta == expected + config.include_all_subdirs = False + meta = plantcv.parallel.metadata_parser(config=config) + assert meta == expected + +def test_plantcv_parallel_metadata_parser_regex(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.delimiter = r'(VIS)_(SV)_(\d+)_(z1)_(h1)_(g0)_(e82)_(\d+)' + + meta = plantcv.parallel.metadata_parser(config=config) + expected = { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'images', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': None, + 'id': '117770', + 'plantbarcode': 'none', + 'treatment': 'none', + 'cartag': 'none', + 'measurementlabel': 'none', + 'other': 'none'} + } + assert meta == expected + + +def test_plantcv_parallel_metadata_parser_images_outside_daterange(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR2) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_outside_daterange", + "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "timestamp"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "NIR"} + config.start_date = "1970-01-01 00_00_00" + config.end_date = "1970-01-01 00_00_00" + config.timestampformat = "%Y-%m-%d %H_%M_%S" + config.imgformat = "jpg" + config.delimiter = r"(NIR)_(SV)_(\d)_(z1)_(h1)_(g0)_(e65)_(\d{4}-\d{2}-\d{2} \d{2}_\d{2}_\d{2})" + + meta = plantcv.parallel.metadata_parser(config=config) + assert meta == {} + + +def test_plantcv_parallel_metadata_parser_no_default_dates(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_no_default_dates", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS", "camera": "SV", "id": "117770"} + config.start_date = None + config.end_date = None + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + + meta = plantcv.parallel.metadata_parser(config=config) + assert meta == METADATA_VIS_ONLY + + +def test_plantcv_parallel_check_date_range_wrongdateformat(): + start_date = 10 + end_date = 10 + img_time = '2010-10-10' + + with pytest.raises(SystemExit, match=r'does not match format'): + date_format = '%Y%m%d' + _ = plantcv.parallel.check_date_range( + start_date, end_date, img_time, date_format) + + +def test_plantcv_parallel_metadata_parser_snapshot_outside_daterange(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_snapshot_outside_daterange", + "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS"} + config.start_date = "1970-01-01 00:00:00.0" + config.end_date = "1970-01-01 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + + meta = plantcv.parallel.metadata_parser(config=config) + + assert meta == {} + + +def test_plantcv_parallel_metadata_parser_fail_images(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_fail_images", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"cartag": "VIS"} + config.start_date = "1970-01-01 00:00:00.0" + config.end_date = "1970-01-01 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.coprocess = "NIR" + + meta = plantcv.parallel.metadata_parser(config=config) + assert meta == METADATA_NIR_ONLY + + +def test_plantcv_parallel_metadata_parser_images_with_frame(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_with_frame", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.coprocess = "NIR" + + meta = plantcv.parallel.metadata_parser(config=config) + + assert meta == { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117770', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none', + 'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg' + }, + 'NIR_SV_0_z1_h1_g0_e65_117779.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'), + 'camera': 'SV', + 'imgtype': 'NIR', + 'zoom': 'z1', + 'exposure': 'e65', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117779', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none' + } + } + + +def test_plantcv_parallel_metadata_parser_images_no_frame(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_no_frame", + "output.json") + config.filename_metadata = ["imgtype", "camera", "X", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.coprocess = "NIR" + + meta = plantcv.parallel.metadata_parser(config=config) + + assert meta == { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': 'none', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117770', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none', + 'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg' + }, + 'NIR_SV_0_z1_h1_g0_e65_117779.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'), + 'camera': 'SV', + 'imgtype': 'NIR', + 'zoom': 'z1', + 'exposure': 'e65', + 'gain': 'g0', + 'frame': 'none', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117779', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none' + } + } + + +def test_plantcv_parallel_metadata_parser_images_no_camera(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images_no_frame", "output.json") + config.filename_metadata = ["imgtype", "X", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "VIS"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.coprocess = "NIR" + + meta = plantcv.parallel.metadata_parser(config=config) + + assert meta == { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'none', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117770', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none', + 'coimg': 'NIR_SV_0_z1_h1_g0_e65_117779.jpg' + }, + 'NIR_SV_0_z1_h1_g0_e65_117779.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'snapshots', 'snapshot57383', 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'), + 'camera': 'none', + 'imgtype': 'NIR', + 'zoom': 'z1', + 'exposure': 'e65', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': '2014-10-22 17:49:35.187', + 'id': '117779', + 'plantbarcode': 'Ca031AA010564', + 'treatment': 'none', + 'cartag': '2143', + 'measurementlabel': 'C002ch_092214_biomass', + 'other': 'none' + } + } + + +def test_plantcv_parallel_job_builder_single_image(): + # Create cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_job_builder_single_image") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(cache_dir, "output.json") + config.tmp_dir = cache_dir + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.img_outdir = cache_dir + config.metadata_filters = {"imgtype": "VIS", "camera": "SV"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.other_args = ["--other", "on"] + config.writeimg = True + + jobs = plantcv.parallel.job_builder(meta=METADATA_VIS_ONLY, config=config) + + image_name = list(METADATA_VIS_ONLY.keys())[0] + result_file = os.path.join(cache_dir, image_name + '.txt') + + expected = ['python', TEST_PIPELINE, '--image', METADATA_VIS_ONLY[image_name]['path'], '--outdir', + cache_dir, '--result', result_file, '--writeimg', '--other', 'on'] + + if len(expected) != len(jobs[0]): + assert False + else: + assert all([i == j] for i, j in zip(jobs[0], expected)) + + +def test_plantcv_parallel_job_builder_coprocess(): + # Create cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_job_builder_coprocess") + os.mkdir(cache_dir) + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + config.json = os.path.join(cache_dir, "output.json") + config.tmp_dir = cache_dir + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.img_outdir = cache_dir + config.metadata_filters = {"imgtype": "VIS", "camera": "SV"} + config.start_date = "2014-10-21 00:00:00.0" + config.end_date = "2014-10-23 00:00:00.0" + config.timestampformat = '%Y-%m-%d %H:%M:%S.%f' + config.imgformat = "jpg" + config.other_args = ["--other", "on"] + config.writeimg = True + config.coprocess = "NIR" + + jobs = plantcv.parallel.job_builder(meta=METADATA_COPROCESS, config=config) + + img_names = list(METADATA_COPROCESS.keys()) + vis_name = img_names[0] + vis_path = METADATA_COPROCESS[vis_name]['path'] + result_file = os.path.join(cache_dir, vis_name + '.txt') + nir_name = img_names[1] + coresult_file = os.path.join(cache_dir, nir_name + '.txt') + + expected = ['python', TEST_PIPELINE, '--image', vis_path, '--outdir', cache_dir, '--result', result_file, + '--coresult', coresult_file, '--writeimg', '--other', 'on'] + + if len(expected) != len(jobs[0]): + assert False + else: + assert all([i == j] for i, j in zip(jobs[0], expected)) + + +def test_plantcv_parallel_multiprocess_create_dask_cluster_local(): + client = plantcv.parallel.create_dask_cluster(cluster="LocalCluster", cluster_config={}) + status = client.status + client.shutdown() + assert status == "running" + + +def test_plantcv_parallel_multiprocess_create_dask_cluster(): + client = plantcv.parallel.create_dask_cluster(cluster="HTCondorCluster", cluster_config={"cores": 1, + "memory": "1GB", + "disk": "1GB"}) + status = client.status + client.shutdown() + assert status == "running" + + +def test_plantcv_parallel_multiprocess_create_dask_cluster_invalid_cluster(): + with pytest.raises(ValueError): + _ = plantcv.parallel.create_dask_cluster(cluster="Skynet", cluster_config={}) + + +def test_plantcv_parallel_convert_datetime_to_unixtime(): + unix_time = plantcv.parallel.convert_datetime_to_unixtime(timestamp_str="1970-01-01", date_format="%Y-%m-%d") + assert unix_time == 0 + + +def test_plantcv_parallel_convert_datetime_to_unixtime_bad_strptime(): + with pytest.raises(SystemExit): + _ = plantcv.parallel.convert_datetime_to_unixtime(timestamp_str="1970-01-01", date_format="%Y-%m") + + +def test_plantcv_parallel_multiprocess(): + image_name = list(METADATA_VIS_ONLY.keys())[0] + image_path = os.path.join(METADATA_VIS_ONLY[image_name]['path'], image_name) + result_file = os.path.join(TEST_TMPDIR, image_name + '.txt') + jobs = [['python', TEST_PIPELINE, '--image', image_path, '--outdir', TEST_TMPDIR, '--result', result_file, + '--writeimg', '--other', 'on']] + # Create a dask LocalCluster client + client = Client(n_workers=1) + plantcv.parallel.multiprocess(jobs, client=client) + assert os.path.exists(result_file) + + +def test_plantcv_parallel_process_results(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_process_results") + os.mkdir(cache_dir) + plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"), + json_file=os.path.join(cache_dir, 'appended_results.json')) + plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"), + json_file=os.path.join(cache_dir, 'appended_results.json')) + # Assert that the output JSON file matches the expected output JSON file + result_file = open(os.path.join(cache_dir, "appended_results.json"), "r") + results = json.load(result_file) + result_file.close() + expected_file = open(os.path.join(PARALLEL_TEST_DATA, "appended_results.json")) + expected = json.load(expected_file) + expected_file.close() + assert results == expected + + +def test_plantcv_parallel_process_results_new_output(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_process_results_new_output") + os.mkdir(cache_dir) + plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"), + json_file=os.path.join(cache_dir, 'new_result.json')) + # Assert output matches expected values + result_file = open(os.path.join(cache_dir, "new_result.json"), "r") + results = json.load(result_file) + result_file.close() + expected_file = open(os.path.join(PARALLEL_TEST_DATA, "new_result.json")) + expected = json.load(expected_file) + expected_file.close() + assert results == expected + + +def test_plantcv_parallel_process_results_valid_json(): + # Test when the file is a valid json file but doesn't contain expected keys + with pytest.raises(RuntimeError): + plantcv.parallel.process_results(job_dir=os.path.join(PARALLEL_TEST_DATA, "results"), + json_file=os.path.join(PARALLEL_TEST_DATA, "valid.json")) + + +def test_plantcv_parallel_process_results_invalid_json(): + # Create a test tmp directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_process_results_invalid_json") + os.mkdir(cache_dir) + # Move the test data to the tmp directory + shutil.copytree(os.path.join(PARALLEL_TEST_DATA, "bad_results"), os.path.join(cache_dir, "bad_results")) + with pytest.raises(RuntimeError): + plantcv.parallel.process_results(job_dir=os.path.join(cache_dir, "bad_results"), + json_file=os.path.join(cache_dir, "bad_results", "invalid.txt")) + + +# #################################################################################################################### +# ########################################### PLANTCV MAIN PACKAGE ################################################### +matplotlib.use('Template') + +TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") +HYPERSPECTRAL_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "hyperspectral_data") +HYPERSPECTRAL_DATA = "darkReference" +HYPERSPECTRAL_WHITE = "darkReference_whiteReference" +HYPERSPECTRAL_DARK = "darkReference_darkReference" +HYPERSPECTRAL_HDR = "darkReference.hdr" +HYPERSPECTRAL_MASK = "darkReference_mask.png" +HYPERSPECTRAL_DATA_NO_DEFAULT = "darkReference2" +HYPERSPECTRAL_HDR_NO_DEFAULT = "darkReference2.hdr" +HYPERSPECTRAL_DATA_APPROX_PSEUDO = "darkReference3" +HYPERSPECTRAL_HDR_APPROX_PSEUDO = "darkReference3.hdr" +HYPERSPECTRAL_HDR_SMALL_RANGE = {'description': '{[HEADWALL Hyperspec III]}', 'samples': '800', 'lines': '1', + 'bands': '978', 'header offset': '0', 'file type': 'ENVI Standard', + 'interleave': 'bil', 'sensor type': 'Unknown', 'byte order': '0', + 'default bands': '159,253,520', 'wavelength units': 'nm', + 'wavelength': ['379.027', '379.663', '380.3', '380.936', '381.573', '382.209']} +FLUOR_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "photosynthesis_data") +FLUOR_IMG = "PSII_PSD_supopt_temp_btx623_22_rep1.DAT" +TEST_COLOR_DIM = (2056, 2454, 3) +TEST_GRAY_DIM = (2056, 2454) +TEST_BINARY_DIM = TEST_GRAY_DIM +TEST_INPUT_COLOR = "input_color_img.jpg" +TEST_INPUT_GRAY = "input_gray_img.jpg" +TEST_INPUT_GRAY_SMALL = "input_gray_img_small.jpg" +TEST_INPUT_BINARY = "input_binary_img.png" +# Image from http://www.libpng.org/pub/png/png-OwlAlpha.html +# This image may be used, edited and reproduced freely. +TEST_INPUT_RGBA = "input_rgba.png" +TEST_INPUT_BAYER = "bayer_img.png" +TEST_INPUT_ROI_CONTOUR = "input_roi_contour.npz" +TEST_INPUT_ROI_HIERARCHY = "input_roi_hierarchy.npz" +TEST_INPUT_CONTOURS = "input_contours.npz" +TEST_INPUT_OBJECT_CONTOURS = "input_object_contours.npz" +TEST_INPUT_OBJECT_HIERARCHY = "input_object_hierarchy.npz" +TEST_VIS = "VIS_SV_0_z300_h1_g0_e85_v500_93054.png" +TEST_NIR = "NIR_SV_0_z300_h1_g0_e15000_v500_93059.png" +TEST_VIS_TV = "VIS_TV_0_z300_h1_g0_e85_v500_93054.png" +TEST_NIR_TV = "NIR_TV_0_z300_h1_g0_e15000_v500_93059.png" +TEST_INPUT_MASK = "input_mask_binary.png" +TEST_INPUT_MASK_OOB = "mask_outbounds.png" +TEST_INPUT_MASK_RESIZE = "input_mask_resize.png" +TEST_INPUT_NIR_MASK = "input_nir.png" +TEST_INPUT_FDARK = "FLUO_TV_dark.png" +TEST_INPUT_FDARK_LARGE = "FLUO_TV_DARK_large" +TEST_INPUT_FMIN = "FLUO_TV_min.png" +TEST_INPUT_FMAX = "FLUO_TV_max.png" +TEST_INPUT_FMASK = "FLUO_TV_MASK.png" +TEST_INPUT_GREENMAG = "input_green-magenta.jpg" +TEST_INPUT_MULTI = "multi_ori_image.jpg" +TEST_INPUT_MULTI_MASK = "multi_ori_mask.jpg" +TEST_INPUT_MULTI_OBJECT = "roi_objects.npz" +TEST_INPUT_MULTI_CONTOUR = "multi_contours.npz" +TEST_INPUT_ClUSTER_CONTOUR = "clusters_i.npz" +TEST_INPUT_MULTI_HIERARCHY = "multi_hierarchy.npz" +TEST_INPUT_VISUALIZE_CONTOUR = "roi_objects_visualize.npz" +TEST_INPUT_VISUALIZE_HIERARCHY = "roi_obj_hierarchy_visualize.npz" +TEST_INPUT_VISUALIZE_CLUSTERS = "clusters_i_visualize.npz" +TEST_INPUT_VISUALIZE_BACKGROUND = "visualize_background_img.png" +TEST_INPUT_GENOTXT = "cluster_names.txt" +TEST_INPUT_GENOTXT_TOO_MANY = "cluster_names_too_many.txt" +TEST_INPUT_CROPPED = 'cropped_img.jpg' +TEST_INPUT_CROPPED_MASK = 'cropped-mask.png' +TEST_INPUT_MARKER = 'seed-image.jpg' +TEST_INPUT_SKELETON = 'input_skeleton.png' +TEST_INPUT_SKELETON_PRUNED = 'input_pruned_skeleton.png' +TEST_FOREGROUND = "TEST_FOREGROUND.jpg" +TEST_BACKGROUND = "TEST_BACKGROUND.jpg" +TEST_PDFS = "naive_bayes_pdfs.txt" +TEST_PDFS_BAD = "naive_bayes_pdfs_bad.txt" +TEST_VIS_SMALL = "setaria_small_vis.png" +TEST_MASK_SMALL = "setaria_small_mask.png" +TEST_VIS_COMP_CONTOUR = "setaria_composed_contours.npz" +TEST_ACUTE_RESULT = np.asarray([[[119, 285]], [[151, 280]], [[168, 267]], [[168, 262]], [[171, 261]], [[224, 269]], + [[246, 271]], [[260, 277]], [[141, 248]], [[183, 194]], [[188, 237]], [[173, 240]], + [[186, 260]], [[147, 244]], [[163, 246]], [[173, 268]], [[170, 272]], [[151, 320]], + [[195, 289]], [[228, 272]], [[210, 272]], [[209, 247]], [[210, 232]]]) +TEST_VIS_SMALL_PLANT = "setaria_small_plant_vis.png" +TEST_MASK_SMALL_PLANT = "setaria_small_plant_mask.png" +TEST_VIS_COMP_CONTOUR_SMALL_PLANT = "setaria_small_plant_composed_contours.npz" +TEST_SAMPLED_RGB_POINTS = "sampled_rgb_points.txt" +TEST_TARGET_IMG = "target_img.png" +TEST_TARGET_IMG_WITH_HEXAGON = "target_img_w_hexagon.png" +TEST_TARGET_IMG_TRIANGLE = "target_img copy.png" +TEST_SOURCE1_IMG = "source1_img.png" +TEST_SOURCE2_IMG = "source2_img.png" +TEST_TARGET_MASK = "mask_img.png" +TEST_TARGET_IMG_COLOR_CARD = "color_card_target.png" +TEST_SOURCE2_MASK = "mask2_img.png" +TEST_TARGET_MATRIX = "target_matrix.npz" +TEST_SOURCE1_MATRIX = "source1_matrix.npz" +TEST_SOURCE2_MATRIX = "source2_matrix.npz" +TEST_MATRIX_B1 = "matrix_b1.npz" +TEST_MATRIX_B2 = "matrix_b2.npz" +TEST_TRANSFORM1 = "transformation_matrix1.npz" +TEST_MATRIX_M1 = "matrix_m1.npz" +TEST_MATRIX_M2 = "matrix_m2.npz" +TEST_S1_CORRECTED = "source_corrected.png" +TEST_SKELETON_OBJECTS = "skeleton_objects.npz" +TEST_SKELETON_HIERARCHIES = "skeleton_hierarchies.npz" +TEST_THERMAL_ARRAY = "thermal_img.npz" +TEST_THERMAL_IMG_MASK = "thermal_img_mask.png" +TEST_INPUT_THERMAL_CSV = "FLIR2600.csv" +PIXEL_VALUES = "pixel_inspector_rgb_values.txt" +TEST_DISCS_MASK = 'discs_binary.png' + + +# ########################## +# Tests for the main package +# ########################## +@pytest.mark.parametrize("debug", ["print", "plot"]) +def test_plantcv_debug(debug, tmpdir): + from plantcv.plantcv._debug import _debug + # Create a test tmp directory + img_outdir = tmpdir.mkdir("sub") + pcv.params.debug = debug + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + _debug(visual=img, filename=os.path.join(img_outdir, TEST_INPUT_COLOR)) + assert True + + +@pytest.mark.parametrize("datatype,value", [[list, []], [int, 2], [float, 2.2], [bool, True], [str, "2"], [dict, {}], + [tuple, ()], [None, None]]) +def test_plantcv_outputs_add_observation(datatype, value): + # Create output instance + outputs = pcv.Outputs() + outputs.add_observation(sample='default', variable='test', trait='test variable', method='type', scale='none', + datatype=datatype, value=value, label=[]) + assert outputs.observations["default"]["test"]["value"] == value + + +def test_plantcv_outputs_add_observation_invalid_type(): + # Create output instance + outputs = pcv.Outputs() + with pytest.raises(RuntimeError): + outputs.add_observation(sample='default', variable='test', trait='test variable', method='type', scale='none', + datatype=list, value=np.array([2]), label=[]) + + +def test_plantcv_outputs_save_results_json_newfile(tmpdir): + # Create a test tmp directory + cache_dir = tmpdir.mkdir("sub") + outfile = os.path.join(cache_dir, "results.json") + # Create output instance + outputs = pcv.Outputs() + outputs.add_observation(sample='default', variable='test', trait='test variable', method='test', scale='none', + datatype=str, value="test", label="none") + outputs.save_results(filename=outfile, outformat="json") + with open(outfile, "r") as fp: + results = json.load(fp) + assert results["observations"]["default"]["test"]["value"] == "test" + + +def test_plantcv_outputs_save_results_json_existing_file(tmpdir): + # Create a test tmp directory + cache_dir = tmpdir.mkdir("sub") + outfile = os.path.join(cache_dir, "data_results.txt") + shutil.copyfile(os.path.join(TEST_DATA, "data_results.txt"), outfile) + # Create output instance + outputs = pcv.Outputs() + outputs.add_observation(sample='default', variable='test', trait='test variable', method='test', scale='none', + datatype=str, value="test", label="none") + outputs.save_results(filename=outfile, outformat="json") + with open(outfile, "r") as fp: + results = json.load(fp) + assert results["observations"]["default"]["test"]["value"] == "test" + + +def test_plantcv_outputs_save_results_csv(tmpdir): + # Create a test tmp directory + cache_dir = tmpdir.mkdir("sub") + outfile = os.path.join(cache_dir, "results.csv") + testfile = os.path.join(TEST_DATA, "data_results.csv") + # Create output instance + outputs = pcv.Outputs() + outputs.add_observation(sample='default', variable='string', trait='string variable', method='string', scale='none', + datatype=str, value="string", label="none") + outputs.add_observation(sample='default', variable='boolean', trait='boolean variable', method='boolean', + scale='none', datatype=bool, value=True, label="none") + outputs.add_observation(sample='default', variable='list', trait='list variable', method='list', + scale='none', datatype=list, value=[1, 2, 3], label=[1, 2, 3]) + outputs.add_observation(sample='default', variable='tuple', trait='tuple variable', method='tuple', + scale='none', datatype=tuple, value=(1, 2), label=(1, 2)) + outputs.add_observation(sample='default', variable='tuple_list', trait='list of tuples variable', + method='tuple_list', scale='none', datatype=list, value=[(1, 2), (3, 4)], label=[1, 2]) + outputs.save_results(filename=outfile, outformat="csv") + with open(outfile, "r") as fp: + results = fp.read() + with open(testfile, "r") as fp: + test_results = fp.read() + assert results == test_results + + +def test_plantcv_transform_warp_smaller(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) + bimg = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY),-1) + bimg_small = cv2.resize(bimg, (200,300)) #not sure why INTER_NEAREST doesn't preserve values + bimg_small[bimg_small>0]=255 + mrow, mcol = bimg_small.shape + vrow, vcol, vdepth = img.shape + pcv.params.debug = None + mask_warped = pcv.transform.warp(bimg_small, img[:,:,2], + pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], + refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) + pcv.params.debug = 'plot' + mask_warped_plot = pcv.transform.warp(bimg_small, img[:,:,2], + pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], + refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) + + assert np.count_nonzero(mask_warped)==93142 + assert np.count_nonzero(mask_warped_plot)==93142 + + +def test_plantcv_transform_warp_larger(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) + gimg = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY),-1) + gimg_large = cv2.resize(gimg, (5000,7000)) + mrow, mcol = gimg_large.shape + vrow, vcol, vdepth = img.shape + pcv.params.debug='print' + mask_warped_print = pcv.transform.warp(gimg_large, img, + pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], + refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) + + assert np.sum(mask_warped_print)==83103814 + + +def test_plantcv_transform_warp_rgbimgerror(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) + gimg = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY),-1) + gimg_large = cv2.resize(gimg, (5000,7000)) + mrow, mcol = gimg_large.shape + vrow, vcol, vdepth = img.shape + + with pytest.raises(RuntimeError): + _ = pcv.transform.warp(img, img, + pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], + refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) + + +def test_plantcv_transform_warp_4ptserror(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) + mrow, mcol, _ = img.shape + vrow, vcol, vdepth = img.shape + + with pytest.raises(RuntimeError): + _ = pcv.transform.warp(img[:,:,0], img, + pts = [(0,0),(mcol-1,0),(0,mrow-1)], + refpts = [(0,0),(vcol-1,0),(0,vrow-1)]) + + with pytest.raises(RuntimeError): + _ = pcv.transform.warp(img[:,:,1], img, + pts = [(0,0),(mcol-1,0),(0,mrow-1)], + refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) + + with pytest.raises(RuntimeError): + _ = pcv.transform.warp(img[:,:,2], img, + pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], + refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1),(0,vrow-1)]) + + +def test_plantcv_acute(): + # Read in test data + mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") + obj_contour = contours_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.acute(obj=obj_contour, win=5, thresh=15, mask=mask) + _ = pcv.acute(obj=obj_contour, win=0, thresh=15, mask=mask) + _ = pcv.acute(obj=np.array(([[213, 190]], [[83, 61]], [[149, 246]])), win=84, thresh=192, mask=mask) + _ = pcv.acute(obj=np.array(([[3, 29]], [[31, 102]], [[161, 63]])), win=148, thresh=56, mask=mask) + _ = pcv.acute(obj=np.array(([[103, 154]], [[27, 227]], [[152, 83]])), win=35, thresh=0, mask=mask) + # Test with debug = None + pcv.params.debug = None + _ = pcv.acute(obj=np.array(([[103, 154]], [[27, 227]], [[152, 83]])), win=35, thresh=0, mask=mask) + _ = pcv.acute(obj=obj_contour, win=0, thresh=15, mask=mask) + homology_pts = pcv.acute(obj=obj_contour, win=5, thresh=15, mask=mask) + assert all([i == j] for i, j in zip(np.shape(homology_pts), (29, 1, 2))) + + +def test_plantcv_acute_vertex(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_acute_vertex") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL)) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") + obj_contour = contours_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img, label="prefix") + _ = pcv.acute_vertex(obj=[], win=5, thresh=15, sep=5, img=img) + _ = pcv.acute_vertex(obj=[], win=.01, thresh=.01, sep=1, img=img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img) + # Test with debug = None + pcv.params.debug = None + acute = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img) + assert all([i == j] for i, j in zip(np.shape(acute), np.shape(TEST_ACUTE_RESULT))) + pcv.outputs.clear() + + +def test_plantcv_acute_vertex_bad_obj(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL)) + obj_contour = np.array([]) + pcv.params.debug = None + result = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img) + assert all([i == j] for i, j in zip(result, [0, ("NA", "NA")])) + pcv.outputs.clear() + + +def test_plantcv_analyze_bound_horizontal(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_horizontal") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img_above_bound_only = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + object_contours = contours_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=300, label="prefix") + pcv.outputs.clear() + _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=100) + _ = pcv.analyze_bound_horizontal(img=img_above_bound_only, obj=object_contours, mask=mask, line_position=1756) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756) + # Test with debug = None + pcv.params.debug = None + _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756) + assert len(pcv.outputs.observations["default"]) == 7 + + +def test_plantcv_analyze_bound_horizontal_grayscale_image(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + object_contours = contours_npz['arr_0'] + # Test with a grayscale reference image and debug="plot" + pcv.params.debug = "plot" + boundary_img1 = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756) + assert len(np.shape(boundary_img1)) == 3 + + +def test_plantcv_analyze_bound_horizontal_neg_y(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_horizontal") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + object_contours = contours_npz['arr_0'] + # Test with debug=None, line position that will trigger -y + pcv.params.debug = "plot" + _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=-1000) + _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=0) + _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=2056) + assert pcv.outputs.observations['default']['height_above_reference']['value'] == 713 + + +def test_plantcv_analyze_bound_vertical(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + object_contours = contours_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000, label="prefix") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000) + # Test with debug = None + pcv.params.debug = None + _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000) + assert pcv.outputs.observations['default']['width_left_reference']['value'] == 94 + + +def test_plantcv_analyze_bound_vertical_grayscale_image(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + object_contours = contours_npz['arr_0'] + # Test with a grayscale reference image and debug="plot" + pcv.params.debug = "plot" + _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000) + assert pcv.outputs.observations['default']['width_left_reference']['value'] == 94 + pcv.outputs.clear() + + +def test_plantcv_analyze_bound_vertical_neg_x(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + object_contours = contours_npz['arr_0'] + # Test with debug="plot", line position that will trigger -x + pcv.params.debug = "plot" + _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=2454) + assert pcv.outputs.observations['default']['width_left_reference']['value'] == 441 + + +def test_plantcv_analyze_bound_vertical_small_x(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + object_contours = contours_npz['arr_0'] + # Test with debug='plot', line position that will trigger -x, and two channel object + pcv.params.debug = "plot" + _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1) + assert pcv.outputs.observations['default']['width_right_reference']['value'] == 441 + + +def test_plantcv_analyze_color(): + # Clear previous outputs + pcv.outputs.clear() + # Test with debug = None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type="all") + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None, label="prefix") + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None) + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='lab') + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='hsv') + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None) + + # Test with debug = "print" + # pcv.params.debug = "print" + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type="all") + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None, label="prefix") + + # Test with debug = "plot" + # pcv.params.debug = "plot" + # _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None) + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='lab') + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='hsv') + # _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type=None) + + # Test with debug = None + # pcv.params.debug = None + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='rgb') + assert pcv.outputs.observations['default']['hue_median']['value'] == 84.0 + +def test_plantcv_analyze_color_incorrect_image(): + img_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + with pytest.raises(RuntimeError): + _ = pcv.analyze_color(rgb_img=img_binary, mask=mask, hist_plot_type=None) +# +# +def test_plantcv_analyze_color_bad_hist_type(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + pcv.params.debug = "plot" + with pytest.raises(RuntimeError): + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='bgr') + + +def test_plantcv_analyze_color_incorrect_hist_plot_type(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + with pytest.raises(RuntimeError): + pcv.params.debug = "plot" + _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type="bgr") + + +def test_plantcv_analyze_nir(): + # Clear previous outputs + pcv.outputs.clear() + # Test with debug=None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + + _ = pcv.analyze_nir_intensity(gray_img=img, mask=mask, bins=256, histplot=True) + result = len(pcv.outputs.observations['default']['nir_frequencies']['value']) + assert result == 256 + + +def test_plantcv_analyze_nir_16bit(): + # Clear previous outputs + pcv.outputs.clear() + # Test with debug=None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + + _ = pcv.analyze_nir_intensity(gray_img=np.uint16(img), mask=mask, bins=256, histplot=True) + result = len(pcv.outputs.observations['default']['nir_frequencies']['value']) + assert result == 256 + + +def test_plantcv_analyze_object(): + # Test with debug = None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + obj_contour = contours_npz['arr_0'] + obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask) + pcv.outputs.clear() + assert len(obj_images) != 0 + + +def test_plantcv_analyze_object_grayscale_input(): + # Test with debug = None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + obj_contour = contours_npz['arr_0'] + obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask) + assert len(obj_images) != 1 + + +def test_plantcv_analyze_object_zero_slope(): + # Test with debug = None + pcv.params.debug = None + # Create a test image + img = np.zeros((50, 50, 3), dtype=np.uint8) + img[10:11, 10:40, 0] = 255 + mask = img[:, :, 0] + obj_contour = np.array([[[10, 10]], [[11, 10]], [[12, 10]], [[13, 10]], [[14, 10]], [[15, 10]], [[16, 10]], + [[17, 10]], [[18, 10]], [[19, 10]], [[20, 10]], [[21, 10]], [[22, 10]], [[23, 10]], + [[24, 10]], [[25, 10]], [[26, 10]], [[27, 10]], [[28, 10]], [[29, 10]], [[30, 10]], + [[31, 10]], [[32, 10]], [[33, 10]], [[34, 10]], [[35, 10]], [[36, 10]], [[37, 10]], + [[38, 10]], [[39, 10]], [[38, 10]], [[37, 10]], [[36, 10]], [[35, 10]], [[34, 10]], + [[33, 10]], [[32, 10]], [[31, 10]], [[30, 10]], [[29, 10]], [[28, 10]], [[27, 10]], + [[26, 10]], [[25, 10]], [[24, 10]], [[23, 10]], [[22, 10]], [[21, 10]], [[20, 10]], + [[19, 10]], [[18, 10]], [[17, 10]], [[16, 10]], [[15, 10]], [[14, 10]], [[13, 10]], + [[12, 10]], [[11, 10]]], dtype=np.int32) + obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask) + assert len(obj_images) != 0 + + +def test_plantcv_analyze_object_longest_axis_2d(): + # Test with debug = None + pcv.params.debug = None + # Create a test image + img = np.zeros((50, 50, 3), dtype=np.uint8) + img[0:5, 45:49, 0] = 255 + img[0:5, 0:5, 0] = 255 + mask = img[:, :, 0] + obj_contour = np.array([[[45, 1]], [[45, 2]], [[45, 3]], [[45, 4]], [[46, 4]], [[47, 4]], [[48, 4]], + [[48, 3]], [[48, 2]], [[48, 1]], [[47, 1]], [[46, 1]], [[1, 1]], [[1, 2]], + [[1, 3]], [[1, 4]], [[2, 4]], [[3, 4]], [[4, 4]], [[4, 3]], [[4, 2]], + [[4, 1]], [[3, 1]], [[2, 1]]], dtype=np.int32) + obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask) + assert len(obj_images) != 0 + + +def test_plantcv_analyze_object_longest_axis_2e(): + # Test with debug = None + pcv.params.debug = None + # Create a test image + img = np.zeros((50, 50, 3), dtype=np.uint8) + img[10:15, 10:40, 0] = 255 + mask = img[:, :, 0] + obj_contour = np.array([[[10, 10]], [[10, 11]], [[10, 12]], [[10, 13]], [[10, 14]], [[11, 14]], [[12, 14]], + [[13, 14]], [[14, 14]], [[15, 14]], [[16, 14]], [[17, 14]], [[18, 14]], [[19, 14]], + [[20, 14]], [[21, 14]], [[22, 14]], [[23, 14]], [[24, 14]], [[25, 14]], [[26, 14]], + [[27, 14]], [[28, 14]], [[29, 14]], [[30, 14]], [[31, 14]], [[32, 14]], [[33, 14]], + [[34, 14]], [[35, 14]], [[36, 14]], [[37, 14]], [[38, 14]], [[39, 14]], [[39, 13]], + [[39, 12]], [[39, 11]], [[39, 10]], [[38, 10]], [[37, 10]], [[36, 10]], [[35, 10]], + [[34, 10]], [[33, 10]], [[32, 10]], [[31, 10]], [[30, 10]], [[29, 10]], [[28, 10]], + [[27, 10]], [[26, 10]], [[25, 10]], [[24, 10]], [[23, 10]], [[22, 10]], [[21, 10]], + [[20, 10]], [[19, 10]], [[18, 10]], [[17, 10]], [[16, 10]], [[15, 10]], [[14, 10]], + [[13, 10]], [[12, 10]], [[11, 10]]], dtype=np.int32) + obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask) + assert len(obj_images) != 0 + + +def test_plantcv_analyze_object_small_contour(): + # Test with debug = None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + obj_contour = [np.array([[[0, 0]], [[0, 50]], [[50, 50]], [[50, 0]]], dtype=np.int32)] + obj_images = pcv.analyze_object(img=img, obj=obj_contour, mask=mask) + assert obj_images is None + + +def test_plantcv_analyze_thermal_values(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_thermal_values") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + # img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_THERMAL_IMG_MASK), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_THERMAL_ARRAY), encoding="latin1") + img = contours_npz['arr_0'] + + pcv.params.debug = None + thermal_hist = pcv.analyze_thermal_values(thermal_array=img, mask=mask, histplot=True) + assert thermal_hist is not None and pcv.outputs.observations['default']['median_temp']['value'] == 33.20922 + +def test_plantcv_apply_mask_white(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_apply_mask_white") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.apply_mask(img=img, mask=mask, mask_color="white") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.apply_mask(img=img, mask=mask, mask_color="white") + # Test with debug = None + pcv.params.debug = None + masked_img = pcv.apply_mask(img=img, mask=mask, mask_color="white") + assert all([i == j] for i, j in zip(np.shape(masked_img), TEST_COLOR_DIM)) + + +def test_plantcv_apply_mask_black(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_apply_mask_black") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.apply_mask(img=img, mask=mask, mask_color="black") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.apply_mask(img=img, mask=mask, mask_color="black") + # Test with debug = None + pcv.params.debug = None + masked_img = pcv.apply_mask(img=img, mask=mask, mask_color="black") + assert all([i == j] for i, j in zip(np.shape(masked_img), TEST_COLOR_DIM)) + + +def test_plantcv_apply_mask_hyperspectral(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_apply_mask_hyperspectral") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + hyper_array = pcv.hyperspectral.read_data(filename=spectral_filename) + + img = np.ones((2056, 2454)) + img_stacked = cv2.merge((img, img, img, img)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.apply_mask(img=img_stacked, mask=img, mask_color="black") + # Test with debug = "plot" + pcv.params.debug = "plot" + masked_array = pcv.apply_mask(img=hyper_array.array_data, mask=img, mask_color="black") + assert np.mean(masked_array) == 13.97111260224949 + + +def test_plantcv_apply_mask_bad_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + with pytest.raises(RuntimeError): + pcv.params.debug = "plot" + _ = pcv.apply_mask(img=img, mask=mask, mask_color="wite") + + +def test_plantcv_auto_crop(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_crop") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1") + roi_contours = [contours[arr_n] for arr_n in contours] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=(20, 10), padding_y=(20, 10), color='black') + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.auto_crop(img=img1, obj=roi_contours[1], color='image') + _ = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=2000, padding_y=2000, color='image') + # Test with debug = None + pcv.params.debug = None + cropped = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=20, padding_y=20, color='black') + x, y, z = np.shape(img1) + x1, y1, z1 = np.shape(cropped) + assert x > x1 + + +def test_plantcv_auto_crop_grayscale_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_crop_grayscale_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY) + contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1") + roi_contours = [contours[arr_n] for arr_n in contours] + # Test with debug = "plot" + pcv.params.debug = "plot" + cropped = pcv.auto_crop(img=gray_img, obj=roi_contours[1], padding_x=20, padding_y=20, color='white') + x, y = np.shape(gray_img) + x1, y1 = np.shape(cropped) + assert x > x1 + + +def test_plantcv_auto_crop_bad_color_input(): + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY) + contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1") + roi_contours = [contours[arr_n] for arr_n in contours] + with pytest.raises(RuntimeError): + _ = pcv.auto_crop(img=gray_img, obj=roi_contours[1], padding_x=20, padding_y=20, color='wite') + + +def test_plantcv_auto_crop_bad_padding_input(): + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY) + contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1") + roi_contours = [contours[arr_n] for arr_n in contours] + with pytest.raises(RuntimeError): + _ = pcv.auto_crop(img=gray_img, obj=roi_contours[1], padding_x="one", padding_y=20, color='white') + + +def test_plantcv_canny_edge_detect(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_canny_edge_detect") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.canny_edge_detect(img=rgb_img, mask=mask, mask_color='white') + _ = pcv.canny_edge_detect(img=img, mask=mask, mask_color='black') + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.canny_edge_detect(img=img, thickness=2) + _ = pcv.canny_edge_detect(img=img) + # Test with debug = None + pcv.params.debug = None + edge_img = pcv.canny_edge_detect(img=img) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(edge_img), TEST_BINARY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(edge_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_canny_edge_detect_bad_input(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_canny_edge_detect") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + with pytest.raises(RuntimeError): + _ = pcv.canny_edge_detect(img=img, mask=mask, mask_color="gray") + + +def test_plantcv_closing(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_closing") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY) + bin_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug=None + pcv.params.debug = None + _ = pcv.closing(gray_img) + # Test with debug='plot' + pcv.params.debug = 'plot' + _ = pcv.closing(bin_img, np.ones((4, 4), np.uint8)) + # Test with debug='print' + pcv.params.debug = 'print' + filtered_img = pcv.closing(bin_img) + assert np.sum(filtered_img) == 16261860 + + +def test_plantcv_closing_bad_input(): + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + with pytest.raises(RuntimeError): + _ = pcv.closing(rgb_img) + + +def test_plantcv_cluster_contours(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + roi_objects = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1") + hierarchy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1") + objs = [roi_objects[arr_n] for arr_n in roi_objects] + obj_hierarchy = hierarchy['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) + _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, show_grid=True) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) + # Test with debug = None + pcv.params.debug = None + clusters_i, contours, hierarchy = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, + nrow=4, ncol=6) + lenori = len(objs) + lenclust = len(clusters_i) + assert lenori > lenclust + + +def test_plantcv_cluster_contours_grayscale_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours_grayscale_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), 0) + roi_objects = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1") + hierachy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1") + objs = [roi_objects[arr_n] for arr_n in roi_objects] + obj_hierarchy = hierachy['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) + # Test with debug = None + pcv.params.debug = None + clusters_i, contours, hierachy = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, + nrow=4, ncol=6) + lenori = len(objs) + lenclust = len(clusters_i) + assert lenori > lenclust + + +def test_plantcv_cluster_contours_splitimg(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours_splitimg") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_CONTOUR), encoding="latin1") + clusters = np.load(os.path.join(TEST_DATA, TEST_INPUT_ClUSTER_CONTOUR), encoding="latin1") + hierachy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1") + cluster_names = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT) + cluster_names_too_many = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT_TOO_MANY) + roi_contours = [contours[arr_n] for arr_n in contours] + cluster_contours = [clusters[arr_n] for arr_n in clusters] + obj_hierarchy = hierachy['arr_0'] + # Test with debug = None + pcv.params.debug = None + _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours, + contours=roi_contours, + hierarchy=obj_hierarchy, outdir=cache_dir, file=None, filenames=None) + _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=[[0]], contours=[], + hierarchy=np.array([[[1, -1, -1, -1]]])) + _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours, + contours=roi_contours, + hierarchy=obj_hierarchy, outdir=cache_dir, file='multi', filenames=None) + _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours, + contours=roi_contours, + hierarchy=obj_hierarchy, outdir=None, file=None, filenames=cluster_names) + _, _, _ = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours, + contours=roi_contours, + hierarchy=obj_hierarchy, outdir=None, file=None, + filenames=cluster_names_too_many) + output_path, imgs, masks = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours, + contours=roi_contours, hierarchy=obj_hierarchy, outdir=None, + file=None, + filenames=None) + assert len(output_path) != 0 + + +def test_plantcv_cluster_contours_splitimg_grayscale(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_cluster_contours_splitimg_grayscale") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), 0) + contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_CONTOUR), encoding="latin1") + clusters = np.load(os.path.join(TEST_DATA, TEST_INPUT_ClUSTER_CONTOUR), encoding="latin1") + hierachy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1") + cluster_names = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT) + cluster_names_too_many = os.path.join(TEST_DATA, TEST_INPUT_GENOTXT_TOO_MANY) + roi_contours = [contours[arr_n] for arr_n in contours] + cluster_contours = [clusters[arr_n] for arr_n in clusters] + obj_hierarchy = hierachy['arr_0'] + pcv.params.debug = None + output_path, imgs, masks = pcv.cluster_contour_splitimg(img=img1, grouped_contour_indexes=cluster_contours, + contours=roi_contours, hierarchy=obj_hierarchy, outdir=None, + file=None, + filenames=None) + assert len(output_path) != 0 + + +def test_plantcv_color_palette(): + # Return a color palette + colors = pcv.color_palette(num=10, saved=False) + assert np.shape(colors) == (10, 3) + + +def test_plantcv_color_palette_random(): + # Return a color palette in random order + pcv.params.color_sequence = "random" + colors = pcv.color_palette(num=10, saved=False) + assert np.shape(colors) == (10, 3) + + +def test_plantcv_color_palette_saved(): + # Return a color palette that was saved + pcv.params.saved_color_scale = [[0, 0, 0], [255, 255, 255]] + colors = pcv.color_palette(num=2, saved=True) + assert colors == [[0, 0, 0], [255, 255, 255]] + + +def test_plantcv_crop(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img, _, _ = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), 'gray') + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.crop(img=img, x=10, y=10, h=50, w=50) + # Test with debug = "plot" + pcv.params.debug = "plot" + cropped = pcv.crop(img=img, x=10, y=10, h=50, w=50) + assert np.shape(cropped) == (50, 50) + + +def test_plantcv_crop_hyperspectral(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_hyperspectral") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = np.ones((2056, 2454)) + img_stacked = cv2.merge((img, img, img, img)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.crop(img=img_stacked, x=10, y=10, h=50, w=50) + # Test with debug = "plot" + pcv.params.debug = "plot" + cropped = pcv.crop(img=img_stacked, x=10, y=10, h=50, w=50) + assert np.shape(cropped) == (50, 50, 4) + + +def test_plantcv_crop_position_mask(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), 'gray') + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + mask_three_channel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + mask_resize = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK_RESIZE), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right") + _ = pcv.crop_position_mask(nir, mask_resize, x=40, y=3, v_pos="top", h_pos="right") + _ = pcv.crop_position_mask(nir, mask_three_channel, x=40, y=3, v_pos="top", h_pos="right") + # Test with debug = "print" with bottom + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="bottom", h_pos="left") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right") + # Test with debug = "plot" with bottom + _ = pcv.crop_position_mask(nir, mask, x=45, y=2, v_pos="bottom", h_pos="left") + # Test with debug = None + pcv.params.debug = None + newmask = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right") + assert np.sum(newmask) == 707115 + + +def test_plantcv_crop_position_mask_color(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_COLOR), mode='native') + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + mask_resize = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK_RESIZE)) + mask_non_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right") + # Test with debug = "print" with bottom + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="bottom", h_pos="left") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right") + # Test with debug = "plot" with bottom + _ = pcv.crop_position_mask(nir, mask, x=45, y=2, v_pos="bottom", h_pos="left") + _ = pcv.crop_position_mask(nir, mask_non_binary, x=45, y=2, v_pos="bottom", h_pos="left") + _ = pcv.crop_position_mask(nir, mask_non_binary, x=45, y=2, v_pos="top", h_pos="left") + _ = pcv.crop_position_mask(nir, mask_non_binary, x=45, y=2, v_pos="bottom", h_pos="right") + _ = pcv.crop_position_mask(nir, mask_resize, x=45, y=2, v_pos="top", h_pos="left") + + # Test with debug = None + pcv.params.debug = None + newmask = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="right") + assert np.sum(newmask) == 707115 + + +def test_plantcv_crop_position_mask_bad_input_x(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + # Read in test data + nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK)) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.crop_position_mask(nir, mask, x=-1, y=-1, v_pos="top", h_pos="right") + + +def test_plantcv_crop_position_mask_bad_input_vpos(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + # Read in test data + nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK)) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="below", h_pos="right") + + +def test_plantcv_crop_position_mask_bad_input_hpos(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_crop_position_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + # Read in test data + nir, path1, filename1 = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK)) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.crop_position_mask(nir, mask, x=40, y=3, v_pos="top", h_pos="starboard") + + +def test_plantcv_dilate(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_dilate") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.dilate(gray_img=img, ksize=5, i=1) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.dilate(gray_img=img, ksize=5, i=1) + # Test with debug = None + pcv.params.debug = None + dilate_img = pcv.dilate(gray_img=img, ksize=5, i=1) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(dilate_img), TEST_BINARY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(dilate_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_dilate_small_k(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = None + pcv.params.debug = None + with pytest.raises(ValueError): + _ = pcv.dilate(img, 1, 1) + + +def test_plantcv_erode(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_erode") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.erode(gray_img=img, ksize=5, i=1) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.erode(gray_img=img, ksize=5, i=1) + # Test with debug = None + pcv.params.debug = None + erode_img = pcv.erode(gray_img=img, ksize=5, i=1) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(erode_img), TEST_BINARY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(erode_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_erode_small_k(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = None + pcv.params.debug = None + with pytest.raises(ValueError): + _ = pcv.erode(img, 1, 1) + + +def test_plantcv_distance_transform(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_distance_transform") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED_MASK), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3) + # Test with debug = None + pcv.params.debug = None + distance_transform_img = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3) + # Assert that the output image has the dimensions of the input image + assert all([i == j] for i, j in zip(np.shape(distance_transform_img), np.shape(mask))) + + +def test_plantcv_fatal_error(): + # Verify that the fatal_error function raises a RuntimeError + with pytest.raises(RuntimeError): + pcv.fatal_error("Test error") + + +def test_plantcv_fill(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.fill(bin_img=img, size=63632) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.fill(bin_img=img, size=63632) + # Test with debug = None + pcv.params.debug = None + fill_img = pcv.fill(bin_img=img, size=63632) + # Assert that the output image has the dimensions of the input image + # assert all([i == j] for i, j in zip(np.shape(fill_img), TEST_BINARY_DIM)) + assert np.sum(fill_img) == 0 + + +def test_plantcv_fill_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill_bad_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + _ = pcv.fill(bin_img=img, size=1) + + +def test_plantcv_fill_holes(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill_holes") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.fill_holes(bin_img=img) + pcv.params.debug = "plot" + _ = pcv.fill_holes(bin_img=img) + # Test with debug = None + pcv.params.debug = None + fill_img = pcv.fill_holes(bin_img=img) + assert np.sum(fill_img) > np.sum(img) + + +def test_plantcv_fill_holes_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill_holes_bad_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + _ = pcv.fill_holes(bin_img=img) + + +def test_plantcv_find_objects(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_find_objects") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.find_objects(img=img, mask=mask) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.find_objects(img=img, mask=mask) + # Test with debug = None + pcv.params.debug = None + contours, hierarchy = pcv.find_objects(img=img, mask=mask) + # Assert the correct number of contours are found + assert len(contours) == 2 + + +def test_plantcv_find_objects_grayscale_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_find_objects_grayscale_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "plot" + pcv.params.debug = "plot" + contours, hierarchy = pcv.find_objects(img=img, mask=mask) + # Assert the correct number of contours are found + assert len(contours) == 2 + + +def test_plantcv_flip(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_flip") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.flip(img=img, direction="horizontal") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.flip(img=img, direction="vertical") + _ = pcv.flip(img=img_binary, direction="vertical") + # Test with debug = None + pcv.params.debug = None + flipped_img = pcv.flip(img=img, direction="horizontal") + assert all([i == j] for i, j in zip(np.shape(flipped_img), TEST_COLOR_DIM)) + + +def test_plantcv_flip_bad_input(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.flip(img=img, direction="vert") + + +def test_plantcv_gaussian_blur(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_gaussian_blur") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None) + _ = pcv.gaussian_blur(img=img_color, ksize=(51, 51), sigma_x=0, sigma_y=None) + # Test with debug = None + pcv.params.debug = None + gaussian_img = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None) + imgavg = np.average(img) + gavg = np.average(gaussian_img) + assert gavg != imgavg + + +def test_plantcv_get_kernel_cross(): + kernel = pcv.get_kernel(size=(3, 3), shape="cross") + assert (kernel == np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])).all() + + +def test_plantcv_get_kernel_rectangle(): + kernel = pcv.get_kernel(size=(3, 3), shape="rectangle") + assert (kernel == np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]])).all() + + +def test_plantcv_get_kernel_ellipse(): + kernel = pcv.get_kernel(size=(3, 3), shape="ellipse") + assert (kernel == np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]])).all() + + +def test_plantcv_get_kernel_bad_input_size(): + with pytest.raises(ValueError): + _ = pcv.get_kernel(size=(1, 1), shape="ellipse") + + +def test_plantcv_get_kernel_bad_input_shape(): + with pytest.raises(RuntimeError): + _ = pcv.get_kernel(size=(3, 1), shape="square") + + +def test_plantcv_get_nir_sv(): + nirpath = pcv.get_nir(TEST_DATA, TEST_VIS) + nirpath1 = os.path.join(TEST_DATA, TEST_NIR) + assert nirpath == nirpath1 + + +def test_plantcv_get_nir_tv(): + nirpath = pcv.get_nir(TEST_DATA, TEST_VIS_TV) + nirpath1 = os.path.join(TEST_DATA, TEST_NIR_TV) + assert nirpath == nirpath1 + + +def test_plantcv_hist_equalization(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hist_equalization") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.hist_equalization(gray_img=img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.hist_equalization(gray_img=img) + # Test with debug = None + pcv.params.debug = None + hist = pcv.hist_equalization(gray_img=img) + histavg = np.average(hist) + imgavg = np.average(img) + assert histavg != imgavg + + +def test_plantcv_hist_equalization_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hist_equalization_bad_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), 1) + # Test with debug = None + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.hist_equalization(gray_img=img) + + +def test_plantcv_image_add(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_image_add") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img2 = np.copy(img1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.image_add(gray_img1=img1, gray_img2=img2) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.image_add(gray_img1=img1, gray_img2=img2) + # Test with debug = None + pcv.params.debug = None + added_img = pcv.image_add(gray_img1=img1, gray_img2=img2) + assert all([i == j] for i, j in zip(np.shape(added_img), TEST_BINARY_DIM)) + + +def test_plantcv_image_subtract(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_image_sub") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # read in images + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img2 = np.copy(img1) + # Test with debug = "print" + pcv.params.debug = 'print' + _ = pcv.image_subtract(img1, img2) + # Test with debug = "plot" + pcv.params.debug = 'plot' + _ = pcv.image_subtract(img1, img2) + # Test with debug = None + pcv.params.debug = None + new_img = pcv.image_subtract(img1, img2) + assert np.array_equal(new_img, np.zeros(np.shape(new_img), np.uint8)) + + +def test_plantcv_image_subtract_fail(): + # read in images + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY)) + # test + with pytest.raises(RuntimeError): + _ = pcv.image_subtract(img1, img2) + + +def test_plantcv_invert(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_invert") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.invert(gray_img=img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.invert(gray_img=img) + # Test with debug = None + pcv.params.debug = None + inverted_img = pcv.invert(gray_img=img) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(inverted_img), TEST_BINARY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(inverted_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_landmark_reference_pt_dist(): + # Clear previous outputs + pcv.outputs.clear() + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_landmark_reference") + os.mkdir(cache_dir) + points_rescaled = [(0.0139, 0.2569), (0.2361, 0.2917), (0.3542, 0.3819), (0.3542, 0.4167), (0.375, 0.4236), + (0.7431, 0.3681), (0.8958, 0.3542), (0.9931, 0.3125), (0.1667, 0.5139), (0.4583, 0.8889), + (0.4931, 0.5903), (0.3889, 0.5694), (0.4792, 0.4306), (0.2083, 0.5417), (0.3194, 0.5278), + (0.3889, 0.375), (0.3681, 0.3472), (0.2361, 0.0139), (0.5417, 0.2292), (0.7708, 0.3472), + (0.6458, 0.3472), (0.6389, 0.5208), (0.6458, 0.625)] + centroid_rescaled = (0.4685, 0.4945) + bottomline_rescaled = (0.4685, 0.2569) + _ = pcv.landmark_reference_pt_dist(points_r=[], centroid_r=('a', 'b'), bline_r=(0, 0)) + _ = pcv.landmark_reference_pt_dist(points_r=[(10, 1000)], centroid_r=(10, 10), bline_r=(10, 10)) + _ = pcv.landmark_reference_pt_dist(points_r=[], centroid_r=(0, 0), bline_r=(0, 0)) + _ = pcv.landmark_reference_pt_dist(points_r=points_rescaled, centroid_r=centroid_rescaled, + bline_r=bottomline_rescaled, label="prefix") + assert len(pcv.outputs.observations['prefix'].keys()) == 8 + + +def test_plantcv_laplace_filter(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_laplace_filter") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.laplace_filter(gray_img=img, ksize=1, scale=1) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.laplace_filter(gray_img=img, ksize=1, scale=1) + # Test with debug = None + pcv.params.debug = None + lp_img = pcv.laplace_filter(gray_img=img, ksize=1, scale=1) + # Assert that the output image has the dimensions of the input image + assert all([i == j] for i, j in zip(np.shape(lp_img), TEST_GRAY_DIM)) + + +def test_plantcv_logical_and(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_logical_and") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img2 = np.copy(img1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.logical_and(bin_img1=img1, bin_img2=img2) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.logical_and(bin_img1=img1, bin_img2=img2) + # Test with debug = None + pcv.params.debug = None + and_img = pcv.logical_and(bin_img1=img1, bin_img2=img2) + assert all([i == j] for i, j in zip(np.shape(and_img), TEST_BINARY_DIM)) + + +def test_plantcv_logical_or(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_logical_or") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img2 = np.copy(img1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.logical_or(bin_img1=img1, bin_img2=img2) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.logical_or(bin_img1=img1, bin_img2=img2) + # Test with debug = None + pcv.params.debug = None + or_img = pcv.logical_or(bin_img1=img1, bin_img2=img2) + assert all([i == j] for i, j in zip(np.shape(or_img), TEST_BINARY_DIM)) + + +def test_plantcv_logical_xor(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_logical_xor") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img2 = np.copy(img1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.logical_xor(bin_img1=img1, bin_img2=img2) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.logical_xor(bin_img1=img1, bin_img2=img2) + # Test with debug = None + pcv.params.debug = None + xor_img = pcv.logical_xor(bin_img1=img1, bin_img2=img2) + assert all([i == j] for i, j in zip(np.shape(xor_img), TEST_BINARY_DIM)) + + +def test_plantcv_median_blur(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_median_blur") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.median_blur(gray_img=img, ksize=5) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.median_blur(gray_img=img, ksize=5) + # Test with debug = None + pcv.params.debug = None + blur_img = pcv.median_blur(gray_img=img, ksize=5) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(blur_img), TEST_BINARY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(blur_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_median_blur_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_median_blur_bad_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + _ = pcv.median_blur(img, 5.) + + +def test_plantcv_naive_bayes_classifier(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_naive_bayes_classifier") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + # Test with debug = None + pcv.params.debug = None + mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(mask), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(mask), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_naive_bayes_classifier_bad_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS_BAD)) + + +def test_plantcv_object_composition(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_object_composition") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1") + object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] + object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") + object_hierarchy = object_hierarchy_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy) + _ = pcv.object_composition(img=img, contours=[], hierarchy=object_hierarchy) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy) + # Test with debug = None + pcv.params.debug = None + contours, mask = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy) + # Assert that the objects have been combined + contour_shape = np.shape(contours) # type: tuple + assert contour_shape[1] == 1 + + +def test_plantcv_object_composition_grayscale_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_object_composition_grayscale_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1") + object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] + object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") + object_hierarchy = object_hierarchy_npz['arr_0'] + # Test with debug = "plot" + pcv.params.debug = "plot" + contours, mask = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy) + # Assert that the objects have been combined + contour_shape = np.shape(contours) # type: tuple + assert contour_shape[1] == 1 + + +def test_plantcv_within_frame(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_within_frame") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + mask_ib = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + mask_oob = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK_OOB), -1) + in_bounds_ib = pcv.within_frame(mask=mask_ib, border_width=1, label="prefix") + in_bounds_oob = pcv.within_frame(mask=mask_oob, border_width=1) + assert (in_bounds_ib is True and in_bounds_oob is False) + + +def test_plantcv_within_frame_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_within_frame") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + grayscale_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + with pytest.raises(RuntimeError): + _ = pcv.within_frame(grayscale_img) + + +def test_plantcv_opening(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_closing") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY) + bin_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug=None + pcv.params.debug = None + _ = pcv.opening(gray_img) + # Test with debug='plot' + pcv.params.debug = 'plot' + _ = pcv.opening(bin_img, np.ones((4, 4), np.uint8)) + # Test with debug='print' + pcv.params.debug = 'print' + filtered_img = pcv.opening(bin_img) + assert np.sum(filtered_img) == 16184595 + + +def test_plantcv_opening_bad_input(): + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) + with pytest.raises(RuntimeError): + _ = pcv.opening(rgb_img) + + +def test_plantcv_output_mask(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_output_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=None, mask_only=False) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=cache_dir, mask_only=False) + _ = pcv.output_mask(img=img_color, mask=mask, filename='test.png', outdir=None, mask_only=False) + # Remove tmp files in working direcctory + shutil.rmtree("ori-images") + shutil.rmtree("mask-images") + # Test with debug = None + pcv.params.debug = None + imgpath, maskpath, analysis_images = pcv.output_mask(img=img, mask=mask, filename='test.png', + outdir=cache_dir, mask_only=False) + assert all([os.path.exists(imgpath) is True, os.path.exists(maskpath) is True]) + + +def test_plantcv_output_mask_true(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_output_mask") + pcv.params.debug_outdir = cache_dir + os.mkdir(cache_dir) + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=cache_dir, mask_only=True) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.output_mask(img=img_color, mask=mask, filename='test.png', outdir=cache_dir, mask_only=True) + pcv.params.debug = None + imgpath, maskpath, analysis_images = pcv.output_mask(img=img, mask=mask, filename='test.png', outdir=cache_dir, + mask_only=False) + assert all([os.path.exists(imgpath) is True, os.path.exists(maskpath) is True]) + + +def test_plantcv_plot_image_matplotlib_input(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + pimg = pcv.visualize.pseudocolor(gray_img=img, mask=mask, min_value=10, max_value=200) + with pytest.raises(RuntimeError): + pcv.plot_image(pimg) + + +def test_plantcv_plot_image_plotnine(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_image_plotnine") + os.mkdir(cache_dir) + dataset = pd.DataFrame({'x': [1, 2, 3, 4], 'y': [1, 2, 3, 4]}) + img = ggplot(data=dataset) + try: + pcv.plot_image(img=img) + except RuntimeError: + assert False + # Assert that the image was plotted without error + assert True + + +def test_plantcv_print_image(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_print_image") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + filename = os.path.join(cache_dir, 'plantcv_print_image.png') + pcv.print_image(img=img, filename=filename) + # Assert that the file was created + assert os.path.exists(filename) is True + + +def test_plantcv_print_image_bad_type(): + with pytest.raises(RuntimeError): + pcv.print_image(img=[], filename="/dev/null") + + +def test_plantcv_print_image_plotnine(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_print_image_plotnine") + os.mkdir(cache_dir) + dataset = pd.DataFrame({'x': [1, 2, 3, 4], 'y': [1, 2, 3, 4]}) + img = ggplot(data=dataset) + filename = os.path.join(cache_dir, 'plantcv_print_image.png') + pcv.print_image(img=img, filename=filename) + # Assert that the file was created + assert os.path.exists(filename) is True + + +def test_plantcv_print_results(tmpdir): + # Create a tmp directory + cache_dir = tmpdir.mkdir("sub") + outfile = os.path.join(cache_dir, "results.json") + pcv.print_results(filename=outfile) + assert os.path.exists(outfile) + + +def test_plantcv_readimage_native(): + # Test with debug = None + pcv.params.debug = None + _ = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR), mode='rgba') + _ = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_COLOR), mode='native') + # Assert that the image name returned equals the name of the input image + # Assert that the path of the image returned equals the path of the input image + # Assert that the dimensions of the returned image equals the expected dimensions + if img_name == TEST_INPUT_COLOR and path == TEST_DATA: + if all([i == j] for i, j in zip(np.shape(img), TEST_COLOR_DIM)): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_readimage_grayscale(): + # Test with debug = None + pcv.params.debug = None + _, _, _ = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_GRAY), mode="grey") + img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_GRAY), mode="gray") + assert len(np.shape(img)) == 2 + + +def test_plantcv_readimage_rgb(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_GRAY), mode="rgb") + assert len(np.shape(img)) == 3 + + +def test_plantcv_readimage_rgba_as_rgb(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_RGBA), mode="native") + assert np.shape(img)[2] == 3 + + +def test_plantcv_readimage_csv(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readimage(filename=os.path.join(TEST_DATA, TEST_INPUT_THERMAL_CSV), mode="csv") + assert len(np.shape(img)) == 2 + + +def test_plantcv_readimage_envi(): + # Test with debug = None + pcv.params.debug = None + array_data = pcv.readimage(filename=os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA), mode="envi") + if sys.version_info[0] < 3: + assert len(array_data.array_type) == 8 + + +def test_plantcv_readimage_bad_file(): + with pytest.raises(RuntimeError): + _ = pcv.readimage(filename=TEST_INPUT_COLOR) + + +def test_plantcv_readbayer_default_bg(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_readbayer_default_bg") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Test with debug = "print" + pcv.params.debug = "print" + _, _, _ = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="BG", alg="default") + # Test with debug = "plot" + pcv.params.debug = "plot" + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="BG", alg="default") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_default_gb(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="GB", alg="default") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_default_rg(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="RG", alg="default") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_default_gr(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="GR", alg="default") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_edgeaware_bg(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="BG", alg="edgeaware") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_edgeaware_gb(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="GB", alg="edgeaware") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_edgeaware_rg(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="RG", alg="edgeaware") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_edgeaware_gr(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="GR", alg="edgeaware") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_variablenumbergradients_bg(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="BG", alg="variablenumbergradients") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_variablenumbergradients_gb(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="GB", alg="variablenumbergradients") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_variablenumbergradients_rg(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="RG", alg="variablenumbergradients") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_variablenumbergradients_gr(): + # Test with debug = None + pcv.params.debug = None + img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), + bayerpattern="GR", alg="variablenumbergradients") + assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) + + +def test_plantcv_readbayer_default_bad_input(): + # Test with debug = None + pcv.params.debug = None + with pytest.raises(RuntimeError): + _, _, _ = pcv.readbayer(filename=os.path.join(TEST_DATA, "no-image.png"), bayerpattern="GR", alg="default") + + +def test_plantcv_rectangle_mask(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rectangle_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="white") + _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="white") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.rectangle_mask(img=img_color, p1=(0, 0), p2=(2454, 2056), color="gray") + # Test with debug = None + pcv.params.debug = None + masked, hist, contour, heir = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="black") + maskedsum = np.sum(masked) + imgsum = np.sum(img) + assert maskedsum < imgsum + + +def test_plantcv_rectangle_mask_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rectangle_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = None + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="whit") + + +def test_plantcv_report_size_marker_detect(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_report_size_marker_detect") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1) + # ROI contour + roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)] + roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32) + + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect', + objcolor='light', thresh_channel='s', thresh=120, label="prefix") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect', + objcolor='light', thresh_channel='s', thresh=120) + # Test with debug = None + pcv.params.debug = None + images = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect', + objcolor='light', thresh_channel='s', thresh=120) + pcv.outputs.clear() + assert len(images) != 0 + + +def test_plantcv_report_size_marker_define(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1) + # ROI contour + roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)] + roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32) + + # Test with debug = None + pcv.params.debug = None + images = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='define', + objcolor='light', thresh_channel='s', thresh=120) + assert len(images) != 0 + + +def test_plantcv_report_size_marker_grayscale_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # ROI contour + roi_contour = [np.array([[[0, 0]], [[0, 49]], [[49, 49]], [[49, 0]]], dtype=np.int32)] + roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32) + + # Test with debug = None + pcv.params.debug = None + images = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='define', + objcolor='light', thresh_channel='s', thresh=120) + assert len(images) != 0 + + +def test_plantcv_report_size_marker_bad_marker_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1) + # ROI contour + roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)] + roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32) + with pytest.raises(RuntimeError): + _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='none', + objcolor='light', thresh_channel='s', thresh=120) + + +def test_plantcv_report_size_marker_bad_threshold_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER), -1) + # ROI contour + roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)] + roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32) + with pytest.raises(RuntimeError): + _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect', + objcolor='light', thresh_channel=None, thresh=120) + + +def test_plantcv_rgb2gray_cmyk(): + # Test with debug = None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + c = pcv.rgb2gray_cmyk(rgb_img=img, channel="c") + # Assert that the output image has the dimensions of the input image but is only a single channel + assert all([i == j] for i, j in zip(np.shape(c), TEST_GRAY_DIM)) + + +def test_plantcv_rgb2gray_cmyk_bad_channel(): + # Test with debug = None + pcv.params.debug = None + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + # Channel S is not in CMYK + _ = pcv.rgb2gray_cmyk(rgb_img=img, channel="s") + + +def test_plantcv_rgb2gray_hsv(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rgb2gray_hsv") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.rgb2gray_hsv(rgb_img=img, channel="s") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.rgb2gray_hsv(rgb_img=img, channel="s") + # Test with debug = None + pcv.params.debug = None + s = pcv.rgb2gray_hsv(rgb_img=img, channel="s") + # Assert that the output image has the dimensions of the input image but is only a single channel + assert all([i == j] for i, j in zip(np.shape(s), TEST_GRAY_DIM)) + + +def test_plantcv_rgb2gray_hsv_bad_input(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.rgb2gray_hsv(rgb_img=img, channel="l") + + +def test_plantcv_rgb2gray_lab(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rgb2gray_lab") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.rgb2gray_lab(rgb_img=img, channel='b') + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.rgb2gray_lab(rgb_img=img, channel='b') + # Test with debug = None + pcv.params.debug = None + b = pcv.rgb2gray_lab(rgb_img=img, channel='b') + # Assert that the output image has the dimensions of the input image but is only a single channel + assert all([i == j] for i, j in zip(np.shape(b), TEST_GRAY_DIM)) + + +def test_plantcv_rgb2gray_lab_bad_input(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.rgb2gray_lab(rgb_img=img, channel="v") + + +def test_plantcv_rgb2gray(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rgb2gray") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.rgb2gray(rgb_img=img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.rgb2gray(rgb_img=img) + # Test with debug = None + pcv.params.debug = None + gray = pcv.rgb2gray(rgb_img=img) + # Assert that the output image has the dimensions of the input image but is only a single channel + assert all([i == j] for i, j in zip(np.shape(gray), TEST_GRAY_DIM)) + + +def test_plantcv_roi2mask(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_acute_vertex") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL)) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") + obj_contour = contours_npz['arr_0'] + pcv.params.debug = "plot" + _ = pcv.roi.roi2mask(img=img, contour=obj_contour) + pcv.params.debug = "print" + mask = pcv.roi.roi2mask(img=img, contour=obj_contour) + assert np.shape(mask)[0:2] == np.shape(img)[0:2] and np.sum(mask) == 255 + + +def test_plantcv_roi_objects(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_objects") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + roi_contour_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_CONTOUR), encoding="latin1") + roi_contour = [roi_contour_npz[arr_n] for arr_n in roi_contour_npz] + roi_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_HIERARCHY), encoding="latin1") + roi_hierarchy = roi_hierarchy_npz['arr_0'] + object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1") + object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] + object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") + object_hierarchy = object_hierarchy_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, + object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="largest") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, + object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="partial") + # Test with debug = None and roi_type = cutto + pcv.params.debug = None + _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, + object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="cutto") + # Test with debug = None + kept_contours, kept_hierarchy, mask, area = pcv.roi_objects(img=img, roi_contour=roi_contour, + roi_hierarchy=roi_hierarchy, + object_contour=object_contours, + obj_hierarchy=object_hierarchy, roi_type="partial") + # Assert that the contours were filtered as expected + assert len(kept_contours) == 1891 + + +def test_plantcv_roi_objects_bad_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + roi_contour_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_CONTOUR), encoding="latin1") + roi_contour = [roi_contour_npz[arr_n] for arr_n in roi_contour_npz] + roi_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_HIERARCHY), encoding="latin1") + roi_hierarchy = roi_hierarchy_npz['arr_0'] + object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1") + object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] + object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") + object_hierarchy = object_hierarchy_npz['arr_0'] + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.roi_objects(img=img, roi_type="cut", roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, + object_contour=object_contours, obj_hierarchy=object_hierarchy) + + +def test_plantcv_roi_objects_grayscale_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_objects_grayscale_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) + roi_contour_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_CONTOUR), encoding="latin1") + roi_contour = [roi_contour_npz[arr_n] for arr_n in roi_contour_npz] + roi_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_ROI_HIERARCHY), encoding="latin1") + roi_hierarchy = roi_hierarchy_npz['arr_0'] + object_contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_CONTOURS), encoding="latin1") + object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] + object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") + object_hierarchy = object_hierarchy_npz['arr_0'] + # Test with debug = "plot" + pcv.params.debug = "plot" + kept_contours, kept_hierarchy, mask, area = pcv.roi_objects(img=img, roi_type="partial", roi_contour=roi_contour, + roi_hierarchy=roi_hierarchy, + object_contour=object_contours, + obj_hierarchy=object_hierarchy) + # Assert that the contours were filtered as expected + assert len(kept_contours) == 1891 + +def test_plantcv_rotate(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + rotated = pcv.rotate(img=img, rotation_deg=45, crop=True) + imgavg = np.average(img) + rotateavg = np.average(rotated) + assert rotateavg != imgavg + +def test_plantcv_transform_rotate(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rotate_img") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.transform.rotate(img=img, rotation_deg=45, crop=True) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.transform.rotate(img=img, rotation_deg=45, crop=True) + # Test with debug = None + pcv.params.debug = None + rotated = pcv.transform.rotate(img=img, rotation_deg=45, crop=True) + imgavg = np.average(img) + rotateavg = np.average(rotated) + assert rotateavg != imgavg + + +def test_plantcv_transform_rotate_gray(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.transform.rotate(img=img, rotation_deg=45, crop=False) + # Test with debug = None + pcv.params.debug = None + rotated = pcv.transform.rotate(img=img, rotation_deg=45, crop=False) + imgavg = np.average(img) + rotateavg = np.average(rotated) + assert rotateavg != imgavg + + +def test_plantcv_scale_features(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_scale_features") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") + obj_contour = contours_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position=50) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position='NA') + # Test with debug = None + pcv.params.debug = None + points_rescaled, centroid_rescaled, bottomline_rescaled = pcv.scale_features(obj=obj_contour, mask=mask, + points=TEST_ACUTE_RESULT, + line_position=50) + assert len(points_rescaled) == 23 + + +def test_plantcv_scale_features_bad_input(): + mask = np.array([]) + obj_contour = np.array([]) + pcv.params.debug = None + result = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position=50) + assert all([i == j] for i, j in zip(result, [("NA", "NA"), ("NA", "NA"), ("NA", "NA")])) + + +def test_plantcv_scharr_filter(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_scharr_filter") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + pcv.params.debug = "print" + # Test with debug = "print" + _ = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1) + # Test with debug = None + pcv.params.debug = None + scharr_img = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1) + # Assert that the output image has the dimensions of the input image + assert all([i == j] for i, j in zip(np.shape(scharr_img), TEST_GRAY_DIM)) + + +def test_plantcv_shift_img(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_shift_img") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.shift_img(img=img, number=300, side="top") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.shift_img(img=img, number=300, side="top") + # Test with debug = "plot" + _ = pcv.shift_img(img=img, number=300, side="bottom") + # Test with debug = "plot" + _ = pcv.shift_img(img=img, number=300, side="right") + # Test with debug = "plot" + _ = pcv.shift_img(img=mask, number=300, side="left") + # Test with debug = None + pcv.params.debug = None + rotated = pcv.shift_img(img=img, number=300, side="top") + imgavg = np.average(img) + shiftavg = np.average(rotated) + assert shiftavg != imgavg + + +def test_plantcv_shift_img_bad_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _ = pcv.shift_img(img=img, number=-300, side="top") + + +def test_plantcv_shift_img_bad_side_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _ = pcv.shift_img(img=img, number=300, side="starboard") + + +def test_plantcv_sobel_filter(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_sobel_filter") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1) + # Test with debug = None + pcv.params.debug = None + sobel_img = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1) + # Assert that the output image has the dimensions of the input image + assert all([i == j] for i, j in zip(np.shape(sobel_img), TEST_GRAY_DIM)) + + +def test_plantcv_stdev_filter(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_sobel_filter") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + pcv.params.debug = "plot" + _ = pcv.stdev_filter(img=img, ksize=11) + pcv.params.debug = "print" + filter_img = pcv.stdev_filter(img=img, ksize=11) + assert (np.shape(filter_img) == np.shape(img)) + + +def test_plantcv_watershed_segmentation(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_watershed_segmentation") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED_MASK), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10, label="prefix") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10) + # Test with debug = None + pcv.params.debug = None + _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10) + assert pcv.outputs.observations['default']['estimated_object_count']['value'] > 9 + + +def test_plantcv_white_balance_gray_16bit(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_white_balance_gray_16bit") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80)) + # Test without an ROI + pcv.params.debug = None + _ = pcv.white_balance(img=img, mode='hist', roi=None) + # Test with debug = None + white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80)) + imgavg = np.average(img) + balancedavg = np.average(white_balanced) + assert balancedavg != imgavg + + +def test_plantcv_white_balance_gray_8bit(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_white_balance_gray_8bit") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK)) + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80)) + # Test without an ROI + pcv.params.debug = None + _ = pcv.white_balance(img=img, mode='hist', roi=None) + # Test with debug = None + white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80)) + imgavg = np.average(img) + balancedavg = np.average(white_balanced) + assert balancedavg != imgavg + + +def test_plantcv_white_balance_rgb(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_white_balance_rgb") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80)) + # Test without an ROI + pcv.params.debug = None + _ = pcv.white_balance(img=img, mode='hist', roi=None) + # Test with debug = None + white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80)) + imgavg = np.average(img) + balancedavg = np.average(white_balanced) + assert balancedavg != imgavg + + +def test_plantcv_white_balance_bad_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1) + # Test with debug = None + with pytest.raises(RuntimeError): + pcv.params.debug = "plot" + _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 5, 5, 5)) + + +def test_plantcv_white_balance_bad_mode_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER)) + # Test with debug = None + with pytest.raises(RuntimeError): + pcv.params.debug = "plot" + _ = pcv.white_balance(img=img, mode='histogram', roi=(5, 5, 80, 80)) + + +def test_plantcv_white_balance_bad_input_int(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1) + # Test with debug = None + with pytest.raises(RuntimeError): + pcv.params.debug = "plot" + _ = pcv.white_balance(img=img, mode='hist', roi=(5., 5, 5, 5)) + + +def test_plantcv_x_axis_pseudolandmarks(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_x_axis_pseudolandmarks_debug") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") + obj_contour = contours_npz['arr_0'] + pcv.params.debug = "print" + _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img, label="prefix") + _ = pcv.x_axis_pseudolandmarks(obj=np.array([[0, 0], [0, 0]]), mask=np.array([[0, 0], [0, 0]]), img=img) + _ = pcv.x_axis_pseudolandmarks(obj=np.array(([[89, 222]], [[252, 39]], [[89, 207]])), + mask=np.array(([[42, 161]], [[2, 47]], [[211, 222]])), img=img) + + _ = pcv.x_axis_pseudolandmarks(obj=(), mask=mask, img=img) + # Test with debug = None + pcv.params.debug = None + top, bottom, center_v = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + pcv.outputs.clear() + assert all([all([i == j] for i, j in zip(np.shape(top), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(bottom), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(center_v), (20, 1, 2)))]) + + +def test_plantcv_x_axis_pseudolandmarks_small_obj(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR_SMALL_PLANT), encoding="latin1") + obj_contour = contours_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _, _, _ = pcv.x_axis_pseudolandmarks(obj=[], mask=mask, img=img) + _, _, _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _, _, _ = pcv.x_axis_pseudolandmarks(obj=[], mask=mask, img=img) + top, bottom, center_v = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + assert all([all([i == j] for i, j in zip(np.shape(top), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(bottom), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(center_v), (20, 1, 2)))]) + + +def test_plantcv_x_axis_pseudolandmarks_bad_input(): + img = np.array([]) + mask = np.array([]) + obj_contour = np.array([]) + pcv.params.debug = None + result = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + assert all([i == j] for i, j in zip(result, [("NA", "NA"), ("NA", "NA"), ("NA", "NA")])) + + +def test_plantcv_x_axis_pseudolandmarks_bad_obj_input(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT)) + with pytest.raises(RuntimeError): + _ = pcv.x_axis_pseudolandmarks(obj=np.array([[-2, -2], [-2, -2]]), mask=np.array([[-2, -2], [-2, -2]]), img=img) + + +def test_plantcv_y_axis_pseudolandmarks(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_y_axis_pseudolandmarks_debug") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") + obj_contour = contours_npz['arr_0'] + pcv.params.debug = "print" + _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img, label="prefix") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + pcv.outputs.clear() + _ = pcv.y_axis_pseudolandmarks(obj=[], mask=mask, img=img) + _ = pcv.y_axis_pseudolandmarks(obj=(), mask=mask, img=img) + _ = pcv.y_axis_pseudolandmarks(obj=np.array(([[89, 222]], [[252, 39]], [[89, 207]])), + mask=np.array(([[42, 161]], [[2, 47]], [[211, 222]])), img=img) + _ = pcv.y_axis_pseudolandmarks(obj=np.array(([[21, 11]], [[159, 155]], [[237, 11]])), + mask=np.array(([[38, 54]], [[144, 169]], [[81, 137]])), img=img) + # Test with debug = None + pcv.params.debug = None + left, right, center_h = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + pcv.outputs.clear() + assert all([all([i == j] for i, j in zip(np.shape(left), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(right), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(center_h), (20, 1, 2)))]) + + +def test_plantcv_y_axis_pseudolandmarks_small_obj(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_y_axis_pseudolandmarks_debug") + os.mkdir(cache_dir) + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR_SMALL_PLANT), encoding="latin1") + obj_contour = contours_npz['arr_0'] + # Test with debug = "print" + pcv.params.debug = "print" + _, _, _ = pcv.y_axis_pseudolandmarks(obj=[], mask=mask, img=img) + _, _, _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + # Test with debug = "plot" + pcv.params.debug = "plot" + pcv.outputs.clear() + left, right, center_h = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + pcv.outputs.clear() + assert all([all([i == j] for i, j in zip(np.shape(left), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(right), (20, 1, 2))), + all([i == j] for i, j in zip(np.shape(center_h), (20, 1, 2)))]) + + +def test_plantcv_y_axis_pseudolandmarks_bad_input(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_y_axis_pseudolandmarks_debug") + os.mkdir(cache_dir) + img = np.array([]) + mask = np.array([]) + obj_contour = np.array([]) + pcv.params.debug = None + result = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) + pcv.outputs.clear() + assert all([i == j] for i, j in zip(result, [("NA", "NA"), ("NA", "NA"), ("NA", "NA")])) + + +def test_plantcv_y_axis_pseudolandmarks_bad_obj_input(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL_PLANT)) + with pytest.raises(RuntimeError): + _ = pcv.y_axis_pseudolandmarks(obj=np.array([[-2, -2], [-2, -2]]), mask=np.array([[-2, -2], [-2, -2]]), img=img) + + +def test_plantcv_background_subtraction(): + # List to hold result of all tests. + truths = [] + fg_img = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND)) + bg_img = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND)) + big_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Testing if background subtraction is actually still working. + # This should return an array whose sum is greater than one + pcv.params.debug = None + fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img) + truths.append(np.sum(fgmask) > 0) + fgmask = pcv.background_subtraction(background_image=big_img, foreground_image=bg_img) + truths.append(np.sum(fgmask) > 0) + # The same foreground subtracted from itself should be 0 + fgmask = pcv.background_subtraction(background_image=fg_img, foreground_image=fg_img) + truths.append(np.sum(fgmask) == 0) + # The same background subtracted from itself should be 0 + fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=bg_img) + truths.append(np.sum(fgmask) == 0) + # All of these should be true for the function to pass testing. + assert (all(truths)) + + +def test_plantcv_background_subtraction_debug(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_background_subtraction_debug") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # List to hold result of all tests. + truths = [] + fg_img = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND)) + bg_img = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND)) + # Test with debug = "print" + pcv.params.debug = "print" + fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img) + truths.append(np.sum(fgmask) > 0) + # Test with debug = "plot" + pcv.params.debug = "plot" + fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img) + truths.append(np.sum(fgmask) > 0) + # All of these should be true for the function to pass testing. + assert (all(truths)) + + +def test_plantcv_background_subtraction_bad_img_type(): + fg_color = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND)) + bg_gray = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND), 0) + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.background_subtraction(background_image=bg_gray, foreground_image=fg_color) + + +def test_plantcv_background_subtraction_different_sizes(): + fg_img = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND)) + bg_img = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND)) + bg_shp = np.shape(bg_img) # type: tuple + bg_img_resized = cv2.resize(bg_img, (int(bg_shp[0] / 2), int(bg_shp[1] / 2)), interpolation=cv2.INTER_AREA) + pcv.params.debug = None + fgmask = pcv.background_subtraction(background_image=bg_img_resized, foreground_image=fg_img) + assert np.sum(fgmask) > 0 + + +def test_plantcv_spatial_clustering_dbscan(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_spatial_clustering_dbscan") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1) + pcv.params.debug = "print" + _ = pcv.spatial_clustering(img, algorithm="DBSCAN", min_cluster_size=10, max_distance=None) + pcv.params.debug = "plot" + spmask = pcv.spatial_clustering(img, algorithm="DBSCAN", min_cluster_size=10, max_distance=None) + assert len(spmask[1]) == 2 + + +def test_plantcv_spatial_clustering_optics(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_spatial_clustering_optics") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1) + pcv.params.debug = None + spmask = pcv.spatial_clustering(img, algorithm="OPTICS", min_cluster_size=100, max_distance=5000) + assert len(spmask[1]) == 2 + + +def test_plantcv_spatial_clustering_badinput(): + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1) + pcv.params.debug = None + with pytest.raises(NameError): + _ = pcv.spatial_clustering(img, algorithm="Hydra", min_cluster_size=5, max_distance=100) + +def test_detect_discs(): + # Read in test data + mask = cv2.imread(os.path.join(TEST_DATA, TEST_DISCS_MASK), -1) + + # Test with debug = None + pcv.params.debug = None + _,coor = pcv.detect_discs(bin_img=mask, ecc_thresh=0.3) + + assert len(coor) == 3 + + +# ############################## +# Tests for the learn subpackage +# ############################## +def test_plantcv_learn_naive_bayes(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_learn_naive_bayes") + os.mkdir(cache_dir) + # Make image and mask directories in the cache directory + imgdir = os.path.join(cache_dir, "images") + maskdir = os.path.join(cache_dir, "masks") + if not os.path.exists(imgdir): + os.mkdir(imgdir) + if not os.path.exists(maskdir): + os.mkdir(maskdir) + # Copy and image and mask to the image/mask directories + shutil.copyfile(os.path.join(TEST_DATA, TEST_VIS_SMALL), os.path.join(imgdir, "image.png")) + shutil.copyfile(os.path.join(TEST_DATA, TEST_MASK_SMALL), os.path.join(maskdir, "image.png")) + # Run the naive Bayes training module + outfile = os.path.join(cache_dir, "naive_bayes_pdfs.txt") + plantcv.learn.naive_bayes(imgdir=imgdir, maskdir=maskdir, outfile=outfile, mkplots=True) + assert os.path.exists(outfile) + + +def test_plantcv_learn_naive_bayes_multiclass(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_learn_naive_bayes_multiclass") + os.mkdir(cache_dir) + # Run the naive Bayes multiclass training module + outfile = os.path.join(cache_dir, "naive_bayes_multiclass_pdfs.txt") + plantcv.learn.naive_bayes_multiclass(samples_file=os.path.join(TEST_DATA, TEST_SAMPLED_RGB_POINTS), outfile=outfile, + mkplots=True) + assert os.path.exists(outfile) + + +# #################################### +# Tests for the morphology subpackage +# #################################### +def test_plantcv_morphology_segment_curvature(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_curvature") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + pcv.params.debug = "print" + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skeleton) + pcv.outputs.clear() + _ = pcv.morphology.segment_curvature(segmented_img, seg_objects, label="prefix") + pcv.params.debug = "plot" + pcv.outputs.clear() + _ = pcv.morphology.segment_curvature(segmented_img, seg_objects) + assert len(pcv.outputs.observations['default']['segment_curvature']['value']) == 22 + + +def test_plantcv_morphology_check_cycles(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_branches") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + pcv.params.debug = "print" + _ = pcv.morphology.check_cycles(mask, label="prefix") + pcv.params.debug = "plot" + _ = pcv.morphology.check_cycles(mask) + pcv.params.debug = None + _ = pcv.morphology.check_cycles(mask) + assert pcv.outputs.observations['default']['num_cycles']['value'] == 1 + + +def test_plantcv_morphology_find_branch_pts(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_branches") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pcv.params.debug = "print" + _ = pcv.morphology.find_branch_pts(skel_img=skeleton, mask=mask, label="prefix") + pcv.params.debug = "plot" + _ = pcv.morphology.find_branch_pts(skel_img=skeleton) + pcv.params.debug = None + branches = pcv.morphology.find_branch_pts(skel_img=skeleton) + assert np.sum(branches) == 9435 + + +def test_plantcv_morphology_find_tips(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_tips") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pcv.params.debug = "print" + _ = pcv.morphology.find_tips(skel_img=skeleton, mask=mask, label="prefix") + pcv.params.debug = "plot" + _ = pcv.morphology.find_tips(skel_img=skeleton) + pcv.params.debug = None + tips = pcv.morphology.find_tips(skel_img=skeleton) + assert np.sum(tips) == 9435 + + +def test_plantcv_morphology_prune(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_pruned") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pcv.params.debug = "print" + _ = pcv.morphology.prune(skel_img=skeleton, size=1) + pcv.params.debug = "plot" + _ = pcv.morphology.prune(skel_img=skeleton, size=1, mask=skeleton) + pcv.params.debug = None + pruned_img, _, _ = pcv.morphology.prune(skel_img=skeleton, size=3) + assert np.sum(pruned_img) < np.sum(skeleton) + + +def test_plantcv_morphology_prune_size0(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_pruned") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pruned_img, _, _ = pcv.morphology.prune(skel_img=skeleton, size=0) + assert np.sum(pruned_img) == np.sum(skeleton) + + +def test_plantcv_morphology_iterative_prune(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_pruned") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pruned_img = pcv.morphology._iterative_prune(skel_img=skeleton, size=3) + assert np.sum(pruned_img) < np.sum(skeleton) + + +def test_plantcv_morphology_segment_skeleton(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_skeleton") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pcv.params.debug = "print" + _ = pcv.morphology.segment_skeleton(skel_img=skeleton, mask=mask) + pcv.params.debug = "plot" + segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton) + assert len(segment_objects) == 73 + + +def test_plantcv_morphology_fill_segments(): + # Clear previous outputs + pcv.outputs.clear() + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + obj_dic = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS)) + obj = [] + for key, val in obj_dic.items(): + obj.append(val) + pcv.params.debug = None + _ = pcv.morphology.fill_segments(mask, obj) + tests = [pcv.outputs.observations['default']['segment_area']['value'][42] == 5529, + pcv.outputs.observations['default']['segment_area']['value'][20] == 5057, + pcv.outputs.observations['default']['segment_area']['value'][49] == 3323] + assert all(tests) + + +def test_plantcv_morphology_fill_segments_with_stem(): + # Clear previous outputs + pcv.outputs.clear() + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + obj_dic = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS)) + obj = [] + for key, val in obj_dic.items(): + obj.append(val) + + stem_obj = obj[0:4] + pcv.params.debug = None + _ = pcv.morphology.fill_segments(mask, obj, stem_obj) + num_objects = len(pcv.outputs.observations['default']['leaf_area']['value']) + assert num_objects == 70 + + +def test_plantcv_morphology_segment_angle(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_angles") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + pcv.params.debug = "print" + segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton) + _ = pcv.morphology.segment_angle(segmented_img=segmented_img, objects=segment_objects, label="prefix") + pcv.params.debug = "plot" + _ = pcv.morphology.segment_angle(segmented_img, segment_objects) + assert len(pcv.outputs.observations['default']['segment_angle']['value']) == 22 + + +def test_plantcv_morphology_segment_angle_overflow(): + # Clear previous outputs + pcv.outputs.clear() + # Don't prune, would usually give overflow error without extra if statement in segment_angle + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_angles") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton) + _ = pcv.morphology.segment_angle(segmented_img, segment_objects) + assert len(pcv.outputs.observations['default']['segment_angle']['value']) == 73 + + +def test_plantcv_morphology_segment_euclidean_length(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_eu_length") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + pcv.params.debug = "print" + segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton) + _ = pcv.morphology.segment_euclidean_length(segmented_img, segment_objects, label="prefix") + pcv.params.debug = "plot" + _ = pcv.morphology.segment_euclidean_length(segmented_img, segment_objects) + assert len(pcv.outputs.observations['default']['segment_eu_length']['value']) == 22 + + +def test_plantcv_morphology_segment_euclidean_length_bad_input(): + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + skel = pcv.morphology.skeletonize(mask=mask) + pcv.params.debug = None + segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skel) + with pytest.raises(RuntimeError): + _ = pcv.morphology.segment_euclidean_length(segmented_img, segment_objects) + + +def test_plantcv_morphology_segment_path_length(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_path_length") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + pcv.params.debug = "print" + segmented_img, segment_objects = pcv.morphology.segment_skeleton(skel_img=skeleton) + _ = pcv.morphology.segment_path_length(segmented_img, segment_objects, label="prefix") + pcv.params.debug = "plot" + _ = pcv.morphology.segment_path_length(segmented_img, segment_objects) + assert len(pcv.outputs.observations['default']['segment_path_length']['value']) == 22 + + +def test_plantcv_morphology_skeletonize(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_skeletonize") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + input_skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pcv.params.debug = "print" + _ = pcv.morphology.skeletonize(mask=mask) + pcv.params.debug = "plot" + _ = pcv.morphology.skeletonize(mask=mask) + pcv.params.debug = None + skeleton = pcv.morphology.skeletonize(mask=mask) + arr = np.array(skeleton == input_skeleton) + assert arr.all() + + +def test_plantcv_morphology_segment_sort(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_sort") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skeleton) + pcv.params.debug = "print" + _ = pcv.morphology.segment_sort(skeleton, seg_objects, mask=skeleton) + pcv.params.debug = "plot" + leaf_obj, stem_obj = pcv.morphology.segment_sort(skeleton, seg_objects) + assert len(leaf_obj) == 36 + + +def test_plantcv_morphology_segment_tangent_angle(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_tangent_angle") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + objects = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS), encoding="latin1") + objs = [objects[arr_n] for arr_n in objects] + pcv.params.debug = "print" + _ = pcv.morphology.segment_tangent_angle(skel, objs, 2, label="prefix") + pcv.params.debug = "plot" + _ = pcv.morphology.segment_tangent_angle(skel, objs, 2) + assert len(pcv.outputs.observations['default']['segment_tangent_angle']['value']) == 73 + + +def test_plantcv_morphology_segment_id(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_tangent_angle") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + objects = np.load(os.path.join(TEST_DATA, TEST_SKELETON_OBJECTS), encoding="latin1") + objs = [objects[arr_n] for arr_n in objects] + pcv.params.debug = "print" + _ = pcv.morphology.segment_id(skel, objs) + pcv.params.debug = "plot" + _, labeled_img = pcv.morphology.segment_id(skel, objs, mask=skel) + assert np.sum(labeled_img) > np.sum(skel) + + +def test_plantcv_morphology_segment_insertion_angle(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pruned, _, _ = pcv.morphology.prune(skel_img=skeleton, size=6) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned) + leaf_obj, stem_obj = pcv.morphology.segment_sort(pruned, seg_objects) + pcv.params.debug = "plot" + _ = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 3, label="prefix") + pcv.params.debug = "print" + _ = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 10) + assert pcv.outputs.observations['default']['segment_insertion_angle']['value'][:6] == ['NA', 'NA', 'NA', + 24.956918822001636, + 50.7313343343401, + 56.427712102130734] + + +def test_plantcv_morphology_segment_insertion_angle_bad_stem(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pruned, _, _ = pcv.morphology.prune(skel_img=skeleton, size=5) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned) + leaf_obj, stem_obj = pcv.morphology.segment_sort(pruned, seg_objects) + stem_obj = [leaf_obj[0], leaf_obj[10]] + with pytest.raises(RuntimeError): + _ = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 10) + + +def test_plantcv_morphology_segment_combine(): + skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skel) + pcv.params.debug = "plot" + # Test with list of IDs input + _, new_objects = pcv.morphology.segment_combine([0, 1], seg_objects, skel) + assert len(new_objects) + 1 == len(seg_objects) + + +def test_plantcv_morphology_segment_combine_lists(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skel) + pcv.params.debug = "print" + # Test with list of lists input + _, new_objects = pcv.morphology.segment_combine([[0, 1, 2], [3, 4]], seg_objects, skel) + assert len(new_objects) + 3 == len(seg_objects) + + +def test_plantcv_morphology_segment_combine_bad_input(): + skel = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON_PRUNED), -1) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=skel) + pcv.params.debug = "plot" + with pytest.raises(RuntimeError): + _, new_objects = pcv.morphology.segment_combine([0.5, 1.5], seg_objects, skel) + + +def test_plantcv_morphology_analyze_stem(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_analyze_stem") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pruned, segmented_img, _ = pcv.morphology.prune(skel_img=skeleton, size=6) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned) + leaf_obj, stem_obj = pcv.morphology.segment_sort(pruned, seg_objects) + pcv.params.debug = "plot" + _ = pcv.morphology.analyze_stem(rgb_img=segmented_img, stem_objects=stem_obj, label="prefix") + pcv.params.debug = "print" + _ = pcv.morphology.analyze_stem(rgb_img=segmented_img, stem_objects=stem_obj) + assert pcv.outputs.observations['default']['stem_angle']['value'] == -12.531776428222656 + + +def test_plantcv_morphology_analyze_stem_bad_angle(): + # Clear previous outputs + pcv.outputs.clear() + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_morphology_segment_insertion_angle") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + skeleton = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_SKELETON), -1) + pruned, _, _ = pcv.morphology.prune(skel_img=skeleton, size=5) + segmented_img, seg_objects = pcv.morphology.segment_skeleton(skel_img=pruned) + _, _ = pcv.morphology.segment_sort(pruned, seg_objects) + # print([stem_obj[3]]) + # stem_obj = [stem_obj[3]] + stem_obj = [[[[1116, 1728]], [[1116, 1]]]] + _ = pcv.morphology.analyze_stem(rgb_img=segmented_img, stem_objects=stem_obj) + assert pcv.outputs.observations['default']['stem_angle']['value'] == 22877334.0 + + +# ######################################## +# Tests for the hyperspectral subpackage +# ######################################## +def test_plantcv_hyperspectral_read_data_default(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_read_data_default") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = "plot" + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + _ = pcv.hyperspectral.read_data(filename=spectral_filename) + pcv.params.debug = "print" + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + assert np.shape(array_data.array_data) == (1, 1600, 978) + + +def test_plantcv_hyperspectral_read_data_no_default_bands(): + pcv.params.debug = "plot" + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA_NO_DEFAULT) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + assert np.shape(array_data.array_data) == (1, 1600, 978) + + +def test_plantcv_hyperspectral_read_data_approx_pseudorgb(): + pcv.params.debug = "plot" + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA_APPROX_PSEUDO) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + assert np.shape(array_data.array_data) == (1, 1600, 978) + + +def test_plantcv_spectral_index_ndvi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ndvi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ndvi(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_ndvi_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ndvi(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.ndvi(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_gdvi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_gdvi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.gdvi(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_gdvi_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.gdvi(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.gdvi(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_savi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_savi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_savi_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.savi(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_pri(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pri") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pri(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_pri_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pri(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.pri(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_ari(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ari") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ari(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_ari_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ari(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.ari(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_ci_rededge(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ci_rededge") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ci_rededge(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_ci_rededge_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ci_rededge(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.ci_rededge(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_cri550(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_cri550") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.cri550(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_cri550_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.cri550(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.cri550(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_cri700(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_cri700") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.cri700(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_cri700_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.cri700(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.cri700(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_egi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_egi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + index_array = pcv.spectral_index.egi(rgb_img=rgb_img) + assert np.shape(index_array.array_data) == (2056, 2454) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_evi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_evi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.evi(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_evi_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.evi(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.evi(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_mari(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_mari") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.mari(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_mari_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.mari(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.mari(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_mcari(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_mcari") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.mcari(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_mcari_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.mcari(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.mcari(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_mtci(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_mtci") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.mtci(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_mtci_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.mtci(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.mtci(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_ndre(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ndre") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ndre(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_ndre_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.ndre(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.ndre(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_psnd_chla(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psnd_chla") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psnd_chla(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_psnd_chla_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psnd_chla(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.psnd_chla(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_psnd_chlb(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psnd_chlb") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psnd_chlb(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_psnd_chlb_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psnd_chlb(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.psnd_chlb(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_psnd_car(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psnd_car") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psnd_car(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_psnd_car_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psnd_car(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.psnd_car(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_psri(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_psri") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psri(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_psri_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.psri(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.psri(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_pssr_chla(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pssr_chla") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pssr_chla(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_pssr_chla_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pssr_chla(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.pssr_chla(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_pssr_chlb(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pssr_chlb") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pssr_chlb(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_pssr_chlb_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pssr_chlb(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.pssr_chlb(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_pssr_car(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_pssr_car") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pssr_car(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_pssr_car_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.pssr_car(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.pssr_car(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_rgri(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_rgri") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.rgri(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_rgri_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.rgri(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.rgri(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_rvsi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_rvsi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.rvsi(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_rvsi_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.rvsi(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.rvsi(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_sipi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_sipi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.sipi(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_sipi_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.sipi(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.sipi(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_sr(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_sr") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.sr(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_sr_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.sr(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.sr(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_vari(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_vari") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.vari(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_vari_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.vari(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.vari(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_vi_green(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_vi_green") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.vi_green(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_vi_green_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.vi_green(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.vi_green(hsi=index_array, distance=20) + + +def test_plantcv_spectral_index_wi(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_wi") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.wi(hsi=array_data, distance=20) + assert np.shape(index_array.array_data) == (1, 1600) and np.nanmax(index_array.pseudo_rgb) == 255 + + +def test_plantcv_spectral_index_wi_bad_input(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + pcv.params.debug = None + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.wi(hsi=array_data, distance=20) + with pytest.raises(RuntimeError): + _ = pcv.spectral_index.wi(hsi=index_array, distance=20) + + +def test_plantcv_hyperspectral_analyze_spectral(): + # Clear previous outputs + pcv.outputs.clear() + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_spectral") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + mask = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + # pcv.params.debug = "plot" + # _ = pcv.hyperspectral.analyze_spectral(array=array_data, mask=mask, histplot=True) + # pcv.params.debug = "print" + # _ = pcv.hyperspectral.analyze_spectral(array=array_data, mask=mask, histplot=True, label="prefix") + pcv.params.debug = None + _ = pcv.hyperspectral.analyze_spectral(array=array_data, mask=mask, histplot=True, label="prefix") + assert len(pcv.outputs.observations['prefix']['spectral_frequencies']['value']) == 978 + + +def test_plantcv_hyperspectral_analyze_index(): + # Clear previous outputs + pcv.outputs.clear() + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=801) + mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255 + # pcv.params.debug = "print" + # pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True) + # pcv.params.debug = "plot" + # pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True) + + pcv.params.debug = None + pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True) + + assert pcv.outputs.observations['default']['mean_index_savi']['value'] > 0 + + +def test_plantcv_hyperspectral_analyze_index_set_range(): + # Clear previous outputs + pcv.outputs.clear() + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index_set_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=801) + mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255 + pcv.params.debug = None + pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, histplot=True, min_bin=0, max_bin=1) + assert pcv.outputs.observations['default']['mean_index_savi']['value'] > 0 + + +def test_plantcv_hyperspectral_analyze_index_auto_range(): + # Clear previous outputs + pcv.outputs.clear() + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index_auto_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=801) + mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255 + pcv.params.debug = None + pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, min_bin="auto", max_bin="auto") + assert pcv.outputs.observations['default']['mean_index_savi']['value'] > 0 + + +def test_plantcv_hyperspectral_analyze_index_outside_range_warning(): + import io + from contextlib import redirect_stdout + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_analyze_index_auto_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=801) + mask_img = np.ones(np.shape(index_array.array_data), dtype=np.uint8) * 255 + f = io.StringIO() + with redirect_stdout(f): + pcv.params.debug = None + pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img, min_bin=.5, max_bin=.55, label="i") + out = f.getvalue() + # assert os.listdir(cache_dir) is 0 + assert out[0:10] == 'WARNING!!!' + + +def test_plantcv_hyperspectral_analyze_index_bad_input_mask(): + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=801) + mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK)) + with pytest.raises(RuntimeError): + pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img) + + +def test_plantcv_hyperspectral_analyze_index_bad_input_index(): + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + index_array = pcv.spectral_index.savi(hsi=array_data, distance=801) + mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1) + index_array.array_data = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK)) + with pytest.raises(RuntimeError): + pcv.hyperspectral.analyze_index(index_array=index_array, mask=mask_img) + + +def test_plantcv_hyperspectral_analyze_index_bad_input_datatype(): + pcv.params.debug = None + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array_data = pcv.hyperspectral.read_data(filename=spectral_filename) + mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1) + with pytest.raises(RuntimeError): + pcv.hyperspectral.analyze_index(index_array=array_data, mask=mask_img) + + +def test_plantcv_hyperspectral_calibrate(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_calibrate") + os.mkdir(cache_dir) + raw = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + white = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_WHITE) + dark = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DARK) + raw = pcv.hyperspectral.read_data(filename=raw) + white = pcv.hyperspectral.read_data(filename=white) + dark = pcv.hyperspectral.read_data(filename=dark) + pcv.params.debug = "plot" + _ = pcv.hyperspectral.calibrate(raw_data=raw, white_reference=white, dark_reference=dark) + pcv.params.debug = "print" + calibrated = pcv.hyperspectral.calibrate(raw_data=raw, white_reference=white, dark_reference=dark) + assert np.shape(calibrated.array_data) == (1, 1600, 978) + + +def test_plantcv_hyperspectral_extract_wavelength(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_extract_wavelength") + os.mkdir(cache_dir) + spectral = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + spectral = pcv.hyperspectral.read_data(filename=spectral) + pcv.params.debug = "plot" + _ = pcv.hyperspectral.extract_wavelength(spectral_data=spectral, wavelength=500) + pcv.params.debug = "print" + new = pcv.hyperspectral.extract_wavelength(spectral_data=spectral, wavelength=500) + assert np.shape(new.array_data) == (1, 1600) + + +def test_plantcv_hyperspectral_avg_reflectance(): + spectral = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + mask_img = cv2.imread(os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_MASK), -1) + spectral = pcv.hyperspectral.read_data(filename=spectral) + avg_reflect = pcv.hyperspectral._avg_reflectance(spectral, mask=mask_img) + assert len(avg_reflect) == 978 + + +def test_plantcv_hyperspectral_inverse_covariance(): + spectral = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + spectral = pcv.hyperspectral.read_data(filename=spectral) + inv_cov = pcv.hyperspectral._inverse_covariance(spectral) + assert np.shape(inv_cov) == (978, 978) + + +# ######################################## +# Tests for the photosynthesis subpackage +# ######################################## +def test_plantcv_photosynthesis_read_dat(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_photosynthesis_read_dat") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + pcv.params.debug = "plot" + fluor_filename = os.path.join(FLUOR_TEST_DATA, FLUOR_IMG) + _, _, _ = pcv.photosynthesis.read_cropreporter(filename=fluor_filename) + pcv.params.debug = "print" + fdark, fmin, fmax = pcv.photosynthesis.read_cropreporter(filename=fluor_filename) + assert np.sum(fmin) < np.sum(fmax) + + +def test_plantcv_photosynthesis_analyze_fvfm(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # filename = os.path.join(cache_dir, 'plantcv_fvfm_hist.png') + # Read in test data + fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1) + fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) + fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) + fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000, label="prefix") + # Test with debug = "plot" + pcv.params.debug = "plot" + fvfm_images = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) + assert len(fvfm_images) != 0 + + +def test_plantcv_photosynthesis_analyze_fvfm_print_analysis_results(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1) + fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) + fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) + fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) + _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) + result_file = os.path.join(cache_dir, "results.txt") + pcv.print_results(result_file) + pcv.outputs.clear() + assert os.path.exists(result_file) + + +def test_plantcv_photosynthesis_analyze_fvfm_bad_fdark(): + # Clear previous outputs + pcv.outputs.clear() + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1) + fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) + fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) + fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) + _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark + 3000, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) + check = pcv.outputs.observations['default']['fdark_passed_qc']['value'] is False + assert check + + +def test_plantcv_photosynthesis_analyze_fvfm_bad_input(): + fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) + fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) + fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) + with pytest.raises(RuntimeError): + _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) + + +# ############################## +# Tests for the roi subpackage +# ############################## +def test_plantcv_roi_from_binary_image(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_from_binary_image") + os.mkdir(cache_dir) + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Create a binary image + bin_img = np.zeros(np.shape(rgb_img)[0:2], dtype=np.uint8) + cv2.rectangle(bin_img, (100, 100), (1000, 1000), 255, -1) + # Test with debug = "print" + pcv.params.debug = "print" + pcv.params.debug_outdir = cache_dir + _, _ = pcv.roi.from_binary_image(bin_img=bin_img, img=rgb_img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _, _ = pcv.roi.from_binary_image(bin_img=bin_img, img=rgb_img) + # Test with debug = None + pcv.params.debug = None + roi_contour, roi_hierarchy = pcv.roi.from_binary_image(bin_img=bin_img, img=rgb_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 3600, 1, 2) + + +def test_plantcv_roi_from_binary_image_grayscale_input(): + # Read in a test grayscale image + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Create a binary image + bin_img = np.zeros(np.shape(gray_img)[0:2], dtype=np.uint8) + cv2.rectangle(bin_img, (100, 100), (1000, 1000), 255, -1) + # Test with debug = "plot" + pcv.params.debug = "plot" + roi_contour, roi_hierarchy = pcv.roi.from_binary_image(bin_img=bin_img, img=gray_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 3600, 1, 2) + + +def test_plantcv_roi_from_binary_image_bad_binary_input(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Binary input is required but an RGB input is provided + with pytest.raises(RuntimeError): + _, _ = pcv.roi.from_binary_image(bin_img=rgb_img, img=rgb_img) + + +def test_plantcv_roi_rectangle(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_rectangle") + os.mkdir(cache_dir) + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + pcv.params.debug_outdir = cache_dir + _, _ = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=rgb_img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _, _ = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=rgb_img) + # Test with debug = None + pcv.params.debug = None + roi_contour, roi_hierarchy = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=rgb_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 4, 1, 2) + + +def test_plantcv_roi_rectangle_grayscale_input(): + # Read in a test grayscale image + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "plot" + pcv.params.debug = "plot" + roi_contour, roi_hierarchy = pcv.roi.rectangle(x=100, y=100, h=500, w=500, img=gray_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 4, 1, 2) + + +def test_plantcv_roi_rectangle_out_of_frame(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # The resulting rectangle needs to be within the dimensions of the image + with pytest.raises(RuntimeError): + _, _ = pcv.roi.rectangle(x=100, y=100, h=500, w=3000, img=rgb_img) + + +def test_plantcv_roi_circle(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_circle") + os.mkdir(cache_dir) + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + pcv.params.debug_outdir = cache_dir + _, _ = pcv.roi.circle(x=100, y=100, r=50, img=rgb_img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _, _ = pcv.roi.circle(x=100, y=100, r=50, img=rgb_img) + # Test with debug = None + pcv.params.debug = None + roi_contour, roi_hierarchy = pcv.roi.circle(x=200, y=225, r=75, img=rgb_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 424, 1, 2) + + +def test_plantcv_roi_circle_grayscale_input(): + # Read in a test grayscale image + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "plot" + pcv.params.debug = "plot" + roi_contour, roi_hierarchy = pcv.roi.circle(x=200, y=225, r=75, img=gray_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 424, 1, 2) + + +def test_plantcv_roi_circle_out_of_frame(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # The resulting rectangle needs to be within the dimensions of the image + with pytest.raises(RuntimeError): + _, _ = pcv.roi.circle(x=50, y=225, r=75, img=rgb_img) + + +def test_plantcv_roi_ellipse(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_roi_ellipse") + os.mkdir(cache_dir) + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + pcv.params.debug_outdir = cache_dir + _, _ = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=rgb_img) + # Test with debug = "plot" + pcv.params.debug = "plot" + _, _ = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=rgb_img) + # Test with debug = None + pcv.params.debug = None + roi_contour, roi_hierarchy = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=rgb_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 360, 1, 2) + + +def test_plantcv_roi_ellipse_grayscale_input(): + # Read in a test grayscale image + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "plot" + pcv.params.debug = "plot" + roi_contour, roi_hierarchy = pcv.roi.ellipse(x=200, y=200, r1=75, r2=50, angle=0, img=gray_img) + # Assert the contours and hierarchy lists contain only the ROI + assert np.shape(roi_contour) == (1, 360, 1, 2) + + +def test_plantcv_roi_ellipse_out_of_frame(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # The resulting rectangle needs to be within the dimensions of the image + with pytest.raises(RuntimeError): + _, _ = pcv.roi.ellipse(x=50, y=225, r1=75, r2=50, angle=0, img=rgb_img) + + +def test_plantcv_roi_multi(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.roi.multi(rgb_img, coord=[(25, 120), (100, 100)], radius=20) + # Test with debug = None + pcv.params.debug = None + rois1, roi_hierarchy1 = pcv.roi.multi(rgb_img, coord=(25, 120), radius=20, spacing=(10, 10), nrows=3, ncols=6) + # Assert the contours has 18 ROIs + assert len(rois1) == 18 + + +def test_plantcv_roi_multi_bad_input(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # The user must input a list of custom coordinates OR inputs to make a grid. Not both + with pytest.raises(RuntimeError): + _, _ = pcv.roi.multi(rgb_img, coord=[(25, 120), (100, 100)], radius=20, spacing=(10, 10), nrows=3, ncols=6) + + +def test_plantcv_roi_multi_bad_input_oob(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # nputs to make a grid make ROIs that go off the screen + with pytest.raises(RuntimeError): + _, _ = pcv.roi.multi(rgb_img, coord=(25000, 12000), radius=2, spacing=(1, 1), nrows=3, ncols=6) + + +def test_plantcv_roi_multi_bad_input_oob_list(): + # Read in test RGB image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # All vertices in the list of centers must draw roi's that are inside the image + with pytest.raises(RuntimeError): + _, _ = pcv.roi.multi(rgb_img, coord=[(25000, 25000), (25000, 12000), (12000, 12000)], radius=20) + + +def test_plantcv_roi_custom(): + # Read in test RGB image + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + pcv.params.debug = "plot" + cnt, hier = pcv.roi.custom(img=img, vertices=[[226, 1], [313, 184], [240, 202], [220, 229], [161, 171]]) + assert np.shape(cnt) == (1, 5, 2) + + +def test_plantcv_roi_custom_bad_input(): + # Read in test RGB image + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # ROI goes out of bounds + with pytest.raises(RuntimeError): + _ = pcv.roi.custom(img=img, vertices=[[226, -1], [3130, 1848], [2404, 2029], [2205, 2298], [1617, 1761]]) + + +# ############################## +# Tests for the transform subpackage +# ############################## +def test_plantcv_transform_get_color_matrix(): + # load in target_matrix + matrix_file = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1") + matrix_compare = matrix_file['arr_0'] + # Read in rgb_img and gray-scale mask + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1) + # The result should be a len(np.unique(mask))-1 x 4 matrix + headers, matrix = pcv.transform.get_color_matrix(rgb_img, mask) + assert np.array_equal(matrix, matrix_compare) + + +def test_plantcv_transform_get_color_matrix_img(): + # Read in two gray-scale images + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1) + # The input for rgb_img needs to be an RGB image + with pytest.raises(RuntimeError): + _, _ = pcv.transform.get_color_matrix(rgb_img, mask) + + +def test_plantcv_transform_get_color_matrix_mask(): + # Read in two gray-scale images + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK)) + # The input for rgb_img needs to be an RGB image + with pytest.raises(RuntimeError): + _, _ = pcv.transform.get_color_matrix(rgb_img, mask) + + +def test_plantcv_transform_get_matrix_m(): + # load in comparison matrices + matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1") + matrix_compare_m = matrix_m_file['arr_0'] + matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1") + matrix_compare_b = matrix_b_file['arr_0'] + # read in matrices + t_matrix_file = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1") + t_matrix = t_matrix_file['arr_0'] + s_matrix_file = np.load(os.path.join(TEST_DATA, TEST_SOURCE1_MATRIX), encoding="latin1") + s_matrix = s_matrix_file['arr_0'] + # apply matrices to function + matrix_a, matrix_m, matrix_b = pcv.transform.get_matrix_m(t_matrix, s_matrix) + matrix_compare_m = np.rint(matrix_compare_m) + matrix_compare_b = np.rint(matrix_compare_b) + matrix_m = np.rint(matrix_m) + matrix_b = np.rint(matrix_b) + assert np.array_equal(matrix_m, matrix_compare_m) and np.array_equal(matrix_b, matrix_compare_b) + + +def test_plantcv_transform_get_matrix_m_unequal_data(): + # load in comparison matrices + matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M2), encoding="latin1") + matrix_compare_m = matrix_m_file['arr_0'] + matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B2), encoding="latin1") + matrix_compare_b = matrix_b_file['arr_0'] + # read in matrices + t_matrix_file = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1") + t_matrix = t_matrix_file['arr_0'] + s_matrix_file = np.load(os.path.join(TEST_DATA, TEST_SOURCE2_MATRIX), encoding="latin1") + s_matrix = s_matrix_file['arr_0'] + # apply matrices to function + matrix_a, matrix_m, matrix_b = pcv.transform.get_matrix_m(t_matrix, s_matrix) + matrix_compare_m = np.rint(matrix_compare_m) + matrix_compare_b = np.rint(matrix_compare_b) + matrix_m = np.rint(matrix_m) + matrix_b = np.rint(matrix_b) + assert np.array_equal(matrix_m, matrix_compare_m) and np.array_equal(matrix_b, matrix_compare_b) + + +def test_plantcv_transform_calc_transformation_matrix(): + # load in comparison matrices + matrix_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1") + matrix_compare = matrix_file['arr_0'] + # read in matrices + matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1") + matrix_m = matrix_m_file['arr_0'] + matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1") + matrix_b = matrix_b_file['arr_0'] + # apply to function + _, matrix_t = pcv.transform.calc_transformation_matrix(matrix_m, matrix_b) + matrix_t = np.rint(matrix_t) + matrix_compare = np.rint(matrix_compare) + assert np.array_equal(matrix_t, matrix_compare) + + +def test_plantcv_transform_calc_transformation_matrix_b_incorrect(): + # read in matrices + matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1") + matrix_m = matrix_m_file['arr_0'] + matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1") + matrix_b = matrix_b_file['arr_0'] + matrix_b = np.asmatrix(matrix_b, float) + with pytest.raises(RuntimeError): + _, _ = pcv.transform.calc_transformation_matrix(matrix_m, matrix_b.T) + + +def test_plantcv_transform_calc_transformation_matrix_not_mult(): + # read in matrices + matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1") + matrix_m = matrix_m_file['arr_0'] + matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1") + matrix_b = matrix_b_file['arr_0'] + with pytest.raises(RuntimeError): + _, _ = pcv.transform.calc_transformation_matrix(matrix_m, matrix_b[:3]) + + +def test_plantcv_transform_calc_transformation_matrix_not_mat(): + # read in matrices + matrix_m_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_M1), encoding="latin1") + matrix_m = matrix_m_file['arr_0'] + matrix_b_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1") + matrix_b = matrix_b_file['arr_0'] + with pytest.raises(RuntimeError): + _, _ = pcv.transform.calc_transformation_matrix(matrix_m[:, 1], matrix_b[:, 1]) + + +def test_plantcv_transform_apply_transformation(): + # load corrected image to compare + corrected_compare = cv2.imread(os.path.join(TEST_DATA, TEST_S1_CORRECTED)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform") + os.mkdir(cache_dir) + # Make image and mask directories in the cache directory + imgdir = os.path.join(cache_dir, "images") + # read in matrices + matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1") + matrix_t = matrix_t_file['arr_0'] + # read in images + target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG)) + # Test with debug = "print" + pcv.params.debug = "print" + pcv.params.debug_outdir = imgdir + _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t) + # Test with debug = None + pcv.params.debug = None + corrected_img = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t) + # assert source and corrected have same shape + assert np.array_equal(corrected_img, corrected_compare) + + +def test_plantcv_transform_apply_transformation_incorrect_t(): + # read in matrices + matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_MATRIX_B1), encoding="latin1") + matrix_t = matrix_t_file['arr_0'] + # read in images + target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG)) + with pytest.raises(RuntimeError): + _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t) + + +def test_plantcv_transform_apply_transformation_incorrect_img(): + # read in matrices + matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1") + matrix_t = matrix_t_file['arr_0'] + # read in images + target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + source_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1) + with pytest.raises(RuntimeError): + _ = pcv.transform.apply_transformation_matrix(source_img, target_img, matrix_t) + + +def test_plantcv_transform_save_matrix(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform") + os.mkdir(cache_dir) + # read in matrix + matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1") + matrix_t = matrix_t_file['arr_0'] + # .npz filename + filename = os.path.join(cache_dir, 'test.npz') + pcv.transform.save_matrix(matrix_t, filename) + assert os.path.exists(filename) is True + + +def test_plantcv_transform_save_matrix_incorrect_filename(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform") + os.mkdir(cache_dir) + # read in matrix + matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1") + matrix_t = matrix_t_file['arr_0'] + # .npz filename + filename = "test" + with pytest.raises(RuntimeError): + pcv.transform.save_matrix(matrix_t, filename) + + +def test_plantcv_transform_load_matrix(): + # read in matrix_t + matrix_t_file = np.load(os.path.join(TEST_DATA, TEST_TRANSFORM1), encoding="latin1") + matrix_t = matrix_t_file['arr_0'] + # test load function with matrix_t + matrix_t_loaded = pcv.transform.load_matrix(os.path.join(TEST_DATA, TEST_TRANSFORM1)) + assert np.array_equal(matrix_t, matrix_t_loaded) + + +def test_plantcv_transform_correct_color(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform") + os.mkdir(cache_dir) + # load corrected image to compare + corrected_compare = cv2.imread(os.path.join(TEST_DATA, TEST_S1_CORRECTED)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_correct_color") + os.mkdir(cache_dir) + # Make image and mask directories in the cache directory + imgdir = os.path.join(cache_dir, "images") + matdir = os.path.join(cache_dir, "saved_matrices") + # Read in target, source, and gray-scale mask + target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1) + output_path = os.path.join(matdir) + # Test with debug = "print" + pcv.params.debug = "print" + pcv.params.debug_outdir = imgdir + _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, cache_dir) + # Test with debug = "plot" + pcv.params.debug = "plot" + _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path) + # Test with debug = None + pcv.params.debug = None + _, _, matrix_t, corrected_img = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path) + # assert source and corrected have same shape + assert all([np.array_equal(corrected_img, corrected_compare), + os.path.exists(os.path.join(output_path, "target_matrix.npz")) is True, + os.path.exists(os.path.join(output_path, "source_matrix.npz")) is True, + os.path.exists(os.path.join(output_path, "transformation_matrix.npz")) is True]) + + +def test_plantcv_transform_correct_color_output_dne(): + # load corrected image to compare + corrected_compare = cv2.imread(os.path.join(TEST_DATA, TEST_S1_CORRECTED)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_correct_color_output_dne") + os.mkdir(cache_dir) + # Make image and mask directories in the cache directory + imgdir = os.path.join(cache_dir, "images") + # Read in target, source, and gray-scale mask + target_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + source_img = cv2.imread(os.path.join(TEST_DATA, TEST_SOURCE1_IMG)) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_MASK), -1) + output_path = os.path.join(cache_dir, "saved_matrices_1") # output_directory that does not currently exist + # Test with debug = "print" + pcv.params.debug = "print" + pcv.params.debug_outdir = imgdir + _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path) + # Test with debug = "plot" + pcv.params.debug = "plot" + _, _, _, _ = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path) + # Test with debug = None + pcv.params.debug = None + _, _, matrix_t, corrected_img = pcv.transform.correct_color(target_img, mask, source_img, mask, output_path) + # assert source and corrected have same shape + assert all([np.array_equal(corrected_img, corrected_compare), + os.path.exists(os.path.join(output_path, "target_matrix.npz")) is True, + os.path.exists(os.path.join(output_path, "source_matrix.npz")) is True, + os.path.exists(os.path.join(output_path, "transformation_matrix.npz")) is True]) + + +def test_plantcv_transform_create_color_card_mask(): + # Load target image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_create_color_card_mask") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=(166, 166), + spacing=(21, 21), nrows=6, ncols=4, exclude=[20, 0]) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=(166, 166), + spacing=(21, 21), nrows=6, ncols=4, exclude=[20, 0]) + # Test with debug = None + pcv.params.debug = None + mask = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=(166, 166), + spacing=(21, 21), nrows=6, ncols=4, exclude=[20, 0]) + assert all([i == j] for i, j in zip(np.unique(mask), np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, + 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, + 220], dtype=np.uint8))) + + +def test_plantcv_transform_quick_color_check(): + # Load target image + t_matrix = np.load(os.path.join(TEST_DATA, TEST_TARGET_MATRIX), encoding="latin1") + target_matrix = t_matrix['arr_0'] + s_matrix = np.load(os.path.join(TEST_DATA, TEST_SOURCE1_MATRIX), encoding="latin1") + source_matrix = s_matrix['arr_0'] + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_quick_color_check") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Test with debug = "print" + pcv.params.debug = "print" + pcv.transform.quick_color_check(target_matrix, source_matrix, num_chips=22) + # Test with debug = "plot" + pcv.params.debug = "plot" + pcv.transform.quick_color_check(target_matrix, source_matrix, num_chips=22) + # Test with debug = None + pcv.params.debug = None + pcv.transform.quick_color_check(target_matrix, source_matrix, num_chips=22) + assert os.path.exists(os.path.join(cache_dir, "color_quick_check.png")) + + +def test_plantcv_transform_find_color_card(): + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + df, start, space = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='adaptgauss', blurry=False, + threshvalue=90) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=start, + spacing=space, nrows=6, ncols=4, exclude=[20, 0]) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=start, + spacing=space, nrows=6, ncols=4, exclude=[20, 0]) + # Test with debug = None + pcv.params.debug = None + mask = pcv.transform.create_color_card_mask(rgb_img=rgb_img, radius=6, start_coord=start, + spacing=space, nrows=6, ncols=4, exclude=[20, 0]) + assert all([i == j] for i, j in zip(np.unique(mask), np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, + 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, + 220], dtype=np.uint8))) + + +def test_plantcv_transform_find_color_card_optional_parameters(): + # Clear previous outputs + pcv.outputs.clear() + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Test with threshold ='normal' + df1, start1, space1 = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='normal', blurry=True, + background='light', threshvalue=90, label="prefix") + assert pcv.outputs.observations["prefix"]["color_chip_size"]["value"] > 15000 + + +def test_plantcv_transform_find_color_card_otsu(): + # Clear previous outputs + pcv.outputs.clear() + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card_otsu") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Test with threshold ='normal' + df1, start1, space1 = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='otsu', blurry=True, + background='light', threshvalue=90, label="prefix") + assert pcv.outputs.observations["prefix"]["color_chip_size"]["value"] > 15000 + + +def test_plantcv_transform_find_color_card_optional_size_parameters(): + # Clear previous outputs + pcv.outputs.clear() + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, record_chip_size="mean") + assert pcv.outputs.observations["default"]["color_chip_size"]["value"] > 15000 + + +def test_plantcv_transform_find_color_card_optional_size_parameters_none(): + # Clear previous outputs + pcv.outputs.clear() + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_COLOR_CARD)) + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_find_color_card") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, record_chip_size=None) + assert pcv.outputs.observations.get("default") is None + + +def test_plantcv_transform_find_color_card_bad_record_chip_size(): + # Clear previous outputs + pcv.outputs.clear() + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + pcv.params.debug = None + _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, record_chip_size='averageeeed') + assert pcv.outputs.observations["default"]["color_chip_size"]["value"] is None + + +def test_plantcv_transform_find_color_card_bad_thresh_input(): + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, threshold_type='gaussian') + + +def test_plantcv_transform_find_color_card_bad_background_input(): + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img, background='lite') + + +def test_plantcv_transform_find_color_card_bad_colorcard(): + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG_WITH_HEXAGON)) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _, _, _ = pcv.transform.find_color_card(rgb_img=rgb_img) + + +def test_plantcv_transform_rescale(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_rescale") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.transform.rescale(gray_img=gray_img, min_value=0, max_value=100) + pcv.params.debug = "plot" + rescaled_img = pcv.transform.rescale(gray_img=gray_img, min_value=0, max_value=100) + assert max(np.unique(rescaled_img)) == 100 + + +def test_plantcv_transform_rescale_bad_input(): + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + with pytest.raises(RuntimeError): + _ = pcv.transform.rescale(gray_img=rgb_img) + + +def test_plantcv_transform_resize(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_trancform_resize") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + size = (100, 100) + # Test with debug "print" + pcv.params.debug = "print" + _ = pcv.transform.resize(img=gray_img, size=size, interpolation="auto") + # Test with debug "plot" + pcv.params.debug = "plot" + resized_img = pcv.transform.resize(img=gray_img, size=size, interpolation="auto") + assert resized_img.shape == size + + +def test_plantcv_transform_resize_unsupported_method(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + with pytest.raises(RuntimeError): + _ = pcv.transform.resize(img=gray_img, size=(100, 100), interpolation="mymethod") + + +def test_plantcv_transform_resize_crop(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + size = (20, 20) + resized_im = pcv.transform.resize(img=gray_img, size=size, interpolation=None) + assert resized_im.shape == size + + +def test_plantcv_transform_resize_pad(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + size = (100, 100) + resized_im = pcv.transform.resize(img=gray_img, size=size, interpolation=None) + assert resized_im.shape == size + + +def test_plantcv_transform_resize_pad_crop_color(): + color_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL)) + size = (100, 100) + resized_im = pcv.transform.resize(img=color_img, size=size, interpolation=None) + assert resized_im.shape == (size[1], size[0], 3) + + +def test_plantcv_transform_resize_factor(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_trancform_resize_factor") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + # Resizing factors + factor_x = 0.5 + factor_y = 0.2 + # Test with debug "print" + pcv.params.debug = "print" + _ = pcv.transform.resize_factor(img=gray_img, factors=(factor_x, factor_y), interpolation="auto") + # Test with debug "plot" + pcv.params.debug = "plot" + resized_img = pcv.transform.resize_factor(img=gray_img, factors=(factor_x, factor_y), interpolation="auto") + output_size = resized_img.shape + expected_size = (int(gray_img.shape[0] * factor_y), int(gray_img.shape[1] * factor_x)) + assert output_size == expected_size + + +def test_plantcv_transform_resize_factor_bad_input(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + with pytest.raises(RuntimeError): + _ = pcv.transform.resize_factor(img=gray_img, factors=(0, 2), interpolation="auto") + + +def test_plantcv_transform_nonuniform_illumination_rgb(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_nonuniform_illumination") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Load rgb image + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_TARGET_IMG)) + pcv.params.debug = "plot" + _ = pcv.transform.nonuniform_illumination(img=rgb_img, ksize=11) + pcv.params.debug = "print" + corrected = pcv.transform.nonuniform_illumination(img=rgb_img, ksize=11) + assert np.mean(corrected) < np.mean(rgb_img) + + +def test_plantcv_transform_nonuniform_illumination_gray(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_transform_nonuniform_illumination") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Load rgb image + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + pcv.params.debug = "plot" + _ = pcv.transform.nonuniform_illumination(img=gray_img, ksize=11) + pcv.params.debug = "print" + corrected = pcv.transform.nonuniform_illumination(img=gray_img, ksize=11) + assert np.shape(corrected) == np.shape(gray_img) + + +# ############################## +# Tests for the threshold subpackage +# ############################## +def test_plantcv_threshold_binary(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_binary") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with object type = dark + pcv.params.debug = None + _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="dark") + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="light") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="light") + # Test with debug = None + pcv.params.debug = None + binary_img = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="light") + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_threshold_binary_incorrect_object_type(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="lite") + + +def test_plantcv_threshold_gaussian(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_gaussian") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with object type = dark + pcv.params.debug = None + _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="dark") + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="light") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="light") + # Test with debug = None + pcv.params.debug = None + binary_img = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="light") + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_threshold_gaussian_incorrect_object_type(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="lite") + + +def test_plantcv_threshold_mean(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_mean") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with object type = dark + pcv.params.debug = None + _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="dark") + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="light") + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="light") + # Test with debug = None + pcv.params.debug = None + binary_img = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="light") + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_threshold_mean_incorrect_object_type(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="lite") + + +def test_plantcv_threshold_otsu(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_otsu") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GREENMAG), -1) + # Test with object set to light + pcv.params.debug = None + _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type="light") + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type='dark') + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type='dark') + # Test with debug = None + pcv.params.debug = None + binary_img = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type='dark') + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_threshold_otsu_incorrect_object_type(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type="lite") + + +def test_plantcv_threshold_custom_range(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "print" + pcv.params.debug = 'print' + # Test channel='gray' + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0], upper_thresh=[255], channel='gray') + _, _ = pcv.threshold.custom_range(gray_img, lower_thresh=[0], upper_thresh=[255], channel='gray') + # Test channel='HSV' + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0, 0], upper_thresh=[255, 255, 255], channel='HSV') + # Test channel='LAB' + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0, 0], upper_thresh=[255, 255, 255], channel='LAB') + pcv.params.debug = 'plot' + # Test channel='RGB' + mask, binary_img = pcv.threshold.custom_range(img, lower_thresh=[0, 0, 0], upper_thresh=[255, 255, 255], + channel='RGB') + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_threshold_custom_range_bad_input_hsv(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2, 2, 2, 2], channel='HSV') + + +def test_plantcv_threshold_custom_range_bad_input_rgb(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2, 2, 2, 2], channel='RGB') + + +def test_plantcv_threshold_custom_range_bad_input_lab(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2, 2, 2], channel='LAB') + + +def test_plantcv_threshold_custom_range_bad_input_gray(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2], channel='gray') + + +def test_plantcv_threshold_custom_range_bad_input_channel(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _, _ = pcv.threshold.custom_range(img, lower_thresh=[0], upper_thresh=[2], channel='CMYK') + + +def test_plantcv_threshold_saturation(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_saturation") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel="all") + # Test with debug = "plot" + pcv.params.debug = "plot" + thresh = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel="any") + assert np.sum(thresh) == 920050455 and len(np.unique(thresh)) == 2 + + +def test_plantcv_threshold_saturation_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_saturation_bad_input") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _ = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel="red") + + +def test_plantcv_threshold_triangle(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_triangle") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="dark", xstep=10) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="light", xstep=10) + # Test with debug = None + pcv.params.debug = None + binary_img = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="light", xstep=10) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_threshold_triangle_incorrect_object_type(): + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + pcv.params.debug = None + _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="lite", xstep=10) + + +def test_plantcv_threshold_texture(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_texture") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) + binary_img = pcv.threshold.texture(gray_img, ksize=6, threshold=7, offset=3, texture_method='dissimilarity', + borders='nearest', max_value=255) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +# ################################### +# Tests for the visualize subpackage +# ################################### +def test_plantcv_visualize_auto_threshold_methods_bad_input(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_threshold_methods") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _ = pcv.visualize.auto_threshold_methods(gray_img=img) + + +def test_plantcv_visualize_auto_threshold_methods(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_threshold_methods") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + pcv.params.debug = "print" + _ = pcv.visualize.auto_threshold_methods(gray_img=img) + pcv.params.debug = "plot" + labeled_imgs = pcv.visualize.auto_threshold_methods(gray_img=img) + assert len(labeled_imgs) == 5 and np.shape(labeled_imgs[0])[0] == np.shape(img)[0] + + +def test_plantcv_visualize_pseudocolor(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + obj_contour = contours_npz['arr_0'] + filename = os.path.join(cache_dir, 'plantcv_pseudo_image.png') + # Test with debug = "print" + pcv.params.debug = "print" + _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) + _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) + + pimg = pcv.visualize.pseudocolor(gray_img=img, mask=mask, min_value=10, max_value=200) + pcv.print_image(pimg, filename) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="image") + _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="image", title="customized title") + _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) + _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="black", obj=obj_contour, axes=False, + colorbar=False) + _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="image", obj=obj_contour, obj_padding=15) + _ = pcv.visualize.pseudocolor(gray_img=img, mask=None, axes=False, colorbar=False) + # Test with debug = None + pcv.params.debug = None + _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) + pseudo_img = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="white") + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(pseudo_img), TEST_BINARY_DIM)): + assert 1 + else: + assert 0 + + +def test_plantcv_visualize_pseudocolor_bad_input(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + _ = pcv.visualize.pseudocolor(gray_img=img) + + +def test_plantcv_visualize_pseudocolor_bad_background(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor_bad_background") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + with pytest.raises(RuntimeError): + _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="pink") + + +def test_plantcv_visualize_pseudocolor_bad_padding(): + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor_bad_background") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + obj_contour = contours_npz['arr_0'] + with pytest.raises(RuntimeError): + _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, obj=obj_contour, obj_padding="pink") + + +def test_plantcv_visualize_colorize_masks(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_naive_bayes_classifier") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], + colors=[(0, 0, 0), (1, 1, 1)]) + # Test with debug = "plot" + pcv.params.debug = "plot" + _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], + colors=[(0, 0, 0), (1, 1, 1)]) + # Test with debug = None + pcv.params.debug = None + colored_img = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], + colors=['red', 'blue']) + # Assert that the output image has the dimensions of the input image + assert not np.average(colored_img) == 0 + + +def test_plantcv_visualize_colorize_masks_bad_input_empty(): + with pytest.raises(RuntimeError): + _ = pcv.visualize.colorize_masks(masks=[], colors=[]) + + +def test_plantcv_visualize_colorize_masks_bad_input_mismatch_number(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + with pytest.raises(RuntimeError): + _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], colors=['red', 'green', 'blue']) + + +def test_plantcv_visualize_colorize_masks_bad_color_input(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = "print" + pcv.params.debug = "print" + mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + with pytest.raises(RuntimeError): + _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], colors=['red', 1.123]) + + +@pytest.mark.parametrize("bins,lb,ub,title", [[200, 0, 255, "Include Title"], [100, None, None, None]]) +def test_plantcv_visualize_histogram(bins, lb, ub, title): + # Test with debug = None + pcv.params.debug = None + # Read test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + fig_hist, hist_df = pcv.visualize.histogram(img=img, mask=mask, bins=bins, lower_bound=lb, upper_bound=ub, + title=title, hist_data=True) + assert all([isinstance(fig_hist, ggplot), isinstance(hist_df, pd.core.frame.DataFrame)]) + + +def test_plantcv_visualize_histogram_no_mask(): + # Test with debug = None + pcv.params.debug = None + # Read test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + fig_hist = pcv.visualize.histogram(img=img, mask=None) + assert isinstance(fig_hist, ggplot) + + +def test_plantcv_visualize_histogram_rgb_img(): + # Test with debug = None + pcv.params.debug = None + # Test RGB input image + img_rgb = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + fig_hist = pcv.visualize.histogram(img=img_rgb) + assert isinstance(fig_hist, ggplot) + + +def test_plantcv_visualize_histogram_multispectral_img(): + # Test with debug = None + pcv.params.debug = None + # Test multi-spectral image + img_rgb = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img_multi = np.concatenate((img_rgb, img_rgb), axis=2) + fig_hist = pcv.visualize.histogram(img=img_multi) + assert isinstance(fig_hist, ggplot) + + +def test_plantcv_visualize_histogram_no_img(): + with pytest.raises(RuntimeError): + _ = pcv.visualize.histogram(img=None) + + +def test_plantcv_visualize_histogram_array(): + # Read test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + _ = pcv.visualize.histogram(img=img[0, :]) + + +def test_plantcv_visualize_clustered_contours(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_hist") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_BACKGROUND), -1) + roi_objects = np.load(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_CONTOUR), encoding="latin1") + hierarchy = np.load(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_HIERARCHY), encoding="latin1") + cluster_i = np.load(os.path.join(TEST_DATA, TEST_INPUT_VISUALIZE_CLUSTERS), encoding="latin1") + objs = [roi_objects[arr_n] for arr_n in roi_objects] + obj_hierarchy = hierarchy['arr_0'] + cluster = [cluster_i[arr_n] for arr_n in cluster_i] + # Test in plot mode + pcv.params.debug = "plot" + # Reset the saved color scale (can be saved between tests) + pcv.params.saved_color_scale = None + _ = pcv.visualize.clustered_contours(img=img1, grouped_contour_indices=cluster, roi_objects=objs, + roi_obj_hierarchy=obj_hierarchy, bounding=False) + # Test in print mode + pcv.params.debug = "print" + # Reset the saved color scale (can be saved between tests) + pcv.params.saved_color_scale = None + cluster_img = pcv.visualize.clustered_contours(img=img, grouped_contour_indices=cluster, roi_objects=objs, + roi_obj_hierarchy=obj_hierarchy, nrow=2, ncol=2, bounding=True) + assert np.sum(cluster_img) > np.sum(img) + + +def test_plantcv_visualize_colorspaces(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_hist") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + pcv.params.debug = "plot" + vis_img_small = pcv.visualize.colorspaces(rgb_img=img, original_img=False) + pcv.params.debug = "print" + vis_img = pcv.visualize.colorspaces(rgb_img=img) + assert np.shape(vis_img)[1] > (np.shape(img)[1]) and np.shape(vis_img_small)[1] > (np.shape(img)[1]) + + +def test_plantcv_visualize_colorspaces_bad_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_hist") + os.mkdir(cache_dir) + pcv.params.debug_outdir = cache_dir + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) + with pytest.raises(RuntimeError): + _ = pcv.visualize.colorspaces(rgb_img=img) + + +def test_plantcv_visualize_overlay_two_imgs(): + pcv.params.debug = None + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs") + os.mkdir(cache_dir) + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY)) + + pcv.params.debug = None + out_img = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2) + sample_pt1 = img1[1445, 1154] + sample_pt2 = img2[1445, 1154] + sample_pt3 = out_img[1445, 1154] + pred_rgb = (sample_pt1 * 0.5) + (sample_pt2 * 0.5) + pred_rgb = pred_rgb.astype(np.uint8) + assert np.array_equal(sample_pt3, pred_rgb) + + +def test_plantcv_visualize_overlay_two_imgs_grayscale(): + pcv.params.debug = None + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs_grayscale") + os.mkdir(cache_dir) + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + out_img = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2) + sample_pt1 = np.array([255, 255, 255], dtype=np.uint8) + sample_pt2 = np.array([255, 255, 255], dtype=np.uint8) + sample_pt3 = out_img[1445, 1154] + pred_rgb = (sample_pt1 * 0.5) + (sample_pt2 * 0.5) + pred_rgb = pred_rgb.astype(np.uint8) + assert np.array_equal(sample_pt3, pred_rgb) + + +def test_plantcv_visualize_overlay_two_imgs_bad_alpha(): + pcv.params.debug = None + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs_bad_alpha") + os.mkdir(cache_dir) + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY)) + alpha = -1 + with pytest.raises(RuntimeError): + _ = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2, alpha=alpha) + + +def test_plantcv_visualize_overlay_two_imgs_size_mismatch(): + pcv.params.debug = None + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_overlay_two_imgs_size_mismatch") + os.mkdir(cache_dir) + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED)) + with pytest.raises(RuntimeError): + _ = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2) + + +# ############################## +# Tests for the utils subpackage +# ############################## +def test_plantcv_utils_json2csv(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_json2csv") + os.mkdir(cache_dir) + plantcv.utils.json2csv(json_file=os.path.join(TEST_DATA, "merged_output.json"), + csv_file=os.path.join(cache_dir, "exports")) + assert all([os.path.exists(os.path.join(cache_dir, "exports-single-value-traits.csv")), + os.path.exists(os.path.join(cache_dir, "exports-multi-value-traits.csv"))]) + + +def test_plantcv_utils_json2csv_no_json(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_json2csv_no_json") + os.mkdir(cache_dir) + with pytest.raises(IOError): + plantcv.utils.json2csv(json_file=os.path.join(TEST_DATA, "not_a_file.json"), + csv_file=os.path.join(cache_dir, "exports")) + + +def test_plantcv_utils_json2csv_bad_json(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_json2csv_bad_json") + os.mkdir(cache_dir) + with pytest.raises(ValueError): + plantcv.utils.json2csv(json_file=os.path.join(TEST_DATA, "incorrect_json_data.txt"), + csv_file=os.path.join(cache_dir, "exports")) + + +def test_plantcv_utils_sample_images_snapshot(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images") + os.mkdir(cache_dir) + snapshot_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + img_outdir = os.path.join(cache_dir, "snapshot") + plantcv.utils.sample_images(source_path=snapshot_dir, dest_path=img_outdir, num=3) + assert os.path.exists(os.path.join(cache_dir, "snapshot")) + + +def test_plantcv_utils_sample_images_flatdir(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images") + os.mkdir(cache_dir) + flat_dir = os.path.join(TEST_DATA) + img_outdir = os.path.join(cache_dir, "images") + plantcv.utils.sample_images(source_path=flat_dir, dest_path=img_outdir, num=30) + random_images = os.listdir(img_outdir) + assert all([len(random_images) == 30, len(np.unique(random_images)) == 30]) + + +def test_plantcv_utils_sample_images_bad_source(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images") + os.mkdir(cache_dir) + fake_dir = os.path.join(TEST_DATA, "snapshot") + img_outdir = os.path.join(cache_dir, "images") + with pytest.raises(IOError): + plantcv.utils.sample_images(source_path=fake_dir, dest_path=img_outdir, num=3) + + +def test_plantcv_utils_sample_images_bad_flat_num(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images") + os.mkdir(cache_dir) + flat_dir = os.path.join(TEST_DATA) + img_outdir = os.path.join(cache_dir, "images") + with pytest.raises(RuntimeError): + plantcv.utils.sample_images(source_path=flat_dir, dest_path=img_outdir, num=300) + + +def test_plantcv_utils_sample_images_bad_phenofront_num(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_sample_images") + os.mkdir(cache_dir) + snapshot_dir = os.path.join(PARALLEL_TEST_DATA, TEST_SNAPSHOT_DIR) + img_outdir = os.path.join(cache_dir, "images") + with pytest.raises(RuntimeError): + plantcv.utils.sample_images(source_path=snapshot_dir, dest_path=img_outdir, num=300) + + +def test_plantcv_utils_tabulate_bayes_classes(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_tabulate_bayes_classes") + os.mkdir(cache_dir) + outfile = os.path.join(cache_dir, "rgb_table.txt") + plantcv.utils.tabulate_bayes_classes(input_file=os.path.join(TEST_DATA, PIXEL_VALUES), output_file=outfile) + table = pd.read_csv(outfile, sep="\t") + assert table.shape == (228, 2) + + +def test_plantcv_utils_tabulate_bayes_classes_missing_input(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_tabulate_bayes_classes_missing_input") + os.mkdir(cache_dir) + outfile = os.path.join(cache_dir, "rgb_table.txt") + with pytest.raises(IOError): + plantcv.utils.tabulate_bayes_classes(input_file=os.path.join(PIXEL_VALUES), output_file=outfile) + + +# ############################## +# Clean up test files +# ############################## +def teardown_function(): + shutil.rmtree(TEST_TMPDIR) From e8c73eefaa6a7456a5a6deff077e5fb1e640fab6 Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Wed, 14 Apr 2021 16:18:15 -0500 Subject: [PATCH 012/178] add get centroids function and update init --- plantcv/plantcv/__init__.py | 6 ++++-- plantcv/plantcv/get_centroids.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 plantcv/plantcv/get_centroids.py diff --git a/plantcv/plantcv/__init__.py b/plantcv/plantcv/__init__.py index dce46cbe2..16b70454f 100644 --- a/plantcv/plantcv/__init__.py +++ b/plantcv/plantcv/__init__.py @@ -75,6 +75,7 @@ from plantcv.plantcv.floodfill import floodfill from plantcv.plantcv import analyze from plantcv.plantcv.detect_discs import detect_discs +from plantcv.plantcv.get_centroids import get_centroids # add new functions to end of lists # Auto versioning @@ -151,6 +152,7 @@ "segment_image_series", "create_labels", "analyze", - "floodfill" - "detect_discs" + "floodfill", + "detect_discs", + "get_centroids" ] diff --git a/plantcv/plantcv/get_centroids.py b/plantcv/plantcv/get_centroids.py new file mode 100644 index 000000000..2be9b2d0d --- /dev/null +++ b/plantcv/plantcv/get_centroids.py @@ -0,0 +1,30 @@ +import cv2 + +def get_centroids(bin_img): + """ Get the coordinates (row,column) of the centroid of each connected + region in a binary image. + + Inputs: + bin_img = Binary image containing the connected regions to consider + + + Returns: + coor = List of coordinates (row,column) of the centroids of the regions + + :param bin_img: numpy.ndarray + :return coor: list + """ + + # find contours in the binary image + _, contours, _ = cv2.findContours(bin_img, cv2.RETR_TREE, + cv2.CHAIN_APPROX_SIMPLE) + coor = [] + for c in contours: + # calculate moments for each contour + M = cv2.moments(c) + # calculate row,col coordinates of centroid + col = int(M["m10"] / M["m00"]) + row = int(M["m01"] / M["m00"]) + coor.append([row,col]) + + return coor From e81c4ec545975cf1d55183226ce5d2589adb7955 Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Wed, 14 Apr 2021 16:22:04 -0500 Subject: [PATCH 013/178] add testing for get_centroids --- tests/tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/tests.py b/tests/tests.py index eaae0b720..3ae18af50 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -3711,6 +3711,16 @@ def test_detect_discs(): assert len(coor) == 3 +def test_get_centroids(): + # Read in test data + mask = cv2.imread(os.path.join(TEST_DATA, TEST_DISCS_MASK), -1) + + # Test with debug = None + pcv.params.debug = None + coor = pcv.get_centroids(bin_img=mask) + + assert len(coor) == 5 + # ############################## # Tests for the learn subpackage From a16ad1709f20917f09d99e2bd5d98e6d35c4ac8a Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Fri, 23 Apr 2021 14:21:39 -0500 Subject: [PATCH 014/178] Create click_count.py --- plantcv/plantcv/visualize/click_count.py | 122 +++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 plantcv/plantcv/visualize/click_count.py diff --git a/plantcv/plantcv/visualize/click_count.py b/plantcv/plantcv/visualize/click_count.py new file mode 100644 index 000000000..cad392226 --- /dev/null +++ b/plantcv/plantcv/visualize/click_count.py @@ -0,0 +1,122 @@ +# Interactively click and count objects in a given image + +import os +import cv2 +import numpy as np +from matplotlib import pyplot as plt +from plantcv.plantcv import params +from plantcv.plantcv import fatal_error +from scipy.spatial import distance + + +def _find_closest(pt, pts): + """ Given coordinates of a point and a list of coordinates of a bunch of points, find the point that has the smallest Euclidean to the given point + + :param pt: (tuple) coordinates of a point + :param pts: (a list of tuples) coordinates of a list of points + :return: index of the closest point and the coordinates of that point + """ + if pt in pts: + return pts.index(pt), pt + dists = distance.cdist([pt], pts, 'euclidean') + idx = np.argmin(dists) + return idx, pts[idx] + + +class ClickCount(object): + def __init__(self, img, figsize=(12, 6)): + print("If you have coordinates to import, the label represent for total count should be 'total'!") + self.img = img + self.points = {} + self.colors = {} + self.count = {} # a dictionary that saves the counts of different classes (labels) + self.figsize = figsize + self.events = [] + self.label = None # current label + self.color = None # current color + self.view_all = None # a flag indicating whether or not view all labels + self.fig = None + self.ax = None + self.p_not_current = None + + def import_coords(self, coords, label="total"): + """ Import center coordinates of already detected objects + Inputs: + coords = list of center coordinates of already detected objects. + label = class label for imported coordinates, by default label="total". + + Returns: + :param coords: list + :param label: string + :return: + """ + if label not in self.points: + self.points[label] = [] + for (y, x) in coords: + self.points[label].append((x, y)) + self.count[label] = len(self.points[label]) + + else: + print(f"Warning: {label} already included and counted, nothing is imported!") + + def view(self, label="total", color="c", view_all=False): + """ + View the label for a specific class label + Inputs: + label = (optional) class label, by default label="total" + color = desired color, by default color="c" + view_all = indicator of whether view all classes, by default view_all=False + + :param label: string + :param color: string + :param view_all: boolean + :return: + """ + if label not in self.points and color in self.colors.values(): + print("Warning: The color assigned to the new class label is already used, " + "if proceeding, items from different classes will not be distinguishable in plots!") + + self.label = label + self.color = color + self.view_all = view_all + + if label not in self.points: + self.points[label] = [] + self.count[label] = 0 + self.colors[label] = color + + print("Warning: this tool is under development and is expected to have updates frequently, " + "please check the documentation page to make sure you are using the correct version!") + self.fig, self.ax = plt.subplots(1, 1, figsize=self.figsize) + + self.events = [] + self.fig.canvas.mpl_connect('button_press_event', self.onclick) + + self.ax.imshow(cv2.cvtColor(self.img, cv2.COLOR_BGR2RGB)) + self.ax.set_title("Please left click on missing pollens\n Right click on those you want to remove") + self.p_not_current = 0 + # if view_all is True, show all already marked markers + if view_all: + for k in self.points.keys(): + for (x, y) in self.points[k]: + self.ax.plot(x, y, marker='x', c=self.colors[k]) + if self.label not in self.points or len(self.points[self.label]) == 0: + self.p_not_current += 1 + else: + for (x, y) in self.points[label]: + self.ax.plot(x, y, marker='x', c=color) + + def onclick(self, event): + self.events.append(event) + if event.button == 1: + self.ax.plot(event.xdata, event.ydata, marker='x', c=self.color) + self.points[self.label].append((event.xdata, event.ydata)) + self.count[self.label] += 1 + else: + idx_remove, _ = _find_closest((event.xdata, event.ydata), self.points[self.label]) + self.points[self.label].pop(idx_remove) + idx_remove = idx_remove + self.p_not_current + ax0plots = self.ax.lines + self.ax.lines.remove(ax0plots[idx_remove]) + self.count[self.label] -= 1 + self.fig.canvas.draw() \ No newline at end of file From a28bcbe386b79335086eb9cd9e4f77d70a294a84 Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Fri, 23 Apr 2021 15:06:41 -0500 Subject: [PATCH 015/178] Update __init__.py --- plantcv/plantcv/visualize/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plantcv/plantcv/visualize/__init__.py b/plantcv/plantcv/visualize/__init__.py index b293f0610..95ca78c4a 100644 --- a/plantcv/plantcv/visualize/__init__.py +++ b/plantcv/plantcv/visualize/__init__.py @@ -11,8 +11,8 @@ from plantcv.plantcv.visualize.hyper_histogram import hyper_histogram from plantcv.plantcv.visualize.pixel_scatter_vis import pixel_scatter_plot from plantcv.plantcv.visualize.chlorophyll_fluorescence import chlorophyll_fluorescence +from plantcv.plantcv.visualize.click_count import ClickCount __all__ = ["pseudocolor", "colorize_masks", "histogram", "colorspaces", "auto_threshold_methods", "overlay_two_imgs", "colorize_label_img", "obj_size_ecdf", "obj_sizes", "hyper_histogram", - "pixel_scatter_plot", "time_lapse_video", "chlorophyll_fluorescence"] - + "pixel_scatter_plot", "time_lapse_video", "chlorophyll_fluorescence", "ClickCount"] From f5e4c151c77f8c7915a4c148563a0f261b058701 Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Fri, 23 Apr 2021 15:06:43 -0500 Subject: [PATCH 016/178] Update tests.py --- tests/tests.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/tests.py b/tests/tests.py index 3ae18af50..25596b7f1 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -6341,6 +6341,41 @@ def test_plantcv_visualize_overlay_two_imgs_size_mismatch(): _ = pcv.visualize.overlay_two_imgs(img1=img1, img2=img2) +def test_plantcv_visualize_click_count(): + pcv.params.debug = None + # generate fake testing image + temp = pcv.transform.rescale(np.random.rand(5,5)) + img = np.stack((temp,)*3, axis=-1) + counter = pcv.visualize.ClickCount(img, figsize=(12, 6)) + assert len(counter.count) == 0 + + # import coordinates + counter.import_coords([(0,0)], label="total") + counter.import_coords([(0,1)], label="c1") + counter.import_coords([(0,2)], label="total") + assert len(counter.count) == 2 + + # view different classes + counter.view() + counter.view(label="c1", color="r", view_all=True) + counter.view(label="c2", color="r", view_all=True) + counter.view(label="c2", color="k", view_all=True) + assert len(counter.points) == 3 + + # create mock events + # left click + e1 = matplotlib.backend_bases.MouseEvent(name="button_press_event", canvas=counter.fig.canvas, x=0, y=0, button=1) + e1.xdata = 0 + e1.ydata = 0 + + # right click + e1_ = matplotlib.backend_bases.MouseEvent(name="button_press_event", canvas=counter.fig.canvas, x=0, y=0, button=3) + e1_.xdata = 0 + e1_.ydata = 0 + counter.onclick(e1) + counter.onclick(e1_) + assert len(counter.events) == 2 + # ############################## # Tests for the utils subpackage # ############################## From 6354400242c490a933a7dd895d090e35302306fa Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Fri, 23 Apr 2021 16:07:41 -0500 Subject: [PATCH 017/178] Create visualize_click_count.md --- docs/visualize_click_count.md | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/visualize_click_count.md diff --git a/docs/visualize_click_count.md b/docs/visualize_click_count.md new file mode 100644 index 000000000..410501356 --- /dev/null +++ b/docs/visualize_click_count.md @@ -0,0 +1,65 @@ +## Click and count objects (developing) + +`ClickCount` is a class that allows user interactive to count objects in images. + +*class* **plantcv.visualize.ClickCount**(*img*, *figsize*=(12, 6)) +- To initialize a `ClickCount` class object, the only required input variable is `img`. +- Another optional input is the desired figure size `figsize`, by default `figsize=(12,6)`. + +### Attributes +**img**: input image. + +**counts**: a dictionary of count for every class. + +### Methods +**import_coords(*coords*, *label*="total")** +- Import available coordinated for a specific class (if applicable). +- **Parameters:** + - coords - a list of available coordintes. + - label - class label for imported coordinates. By default `label="total`. + +**view(*label*="total", *color*="c", *view_all*=False)** +- View marked image, and update markers if needed. +- **Parameters**: + - label - class label to show on the marked image. By default `label="total`. + - color - desired color to show the class. By default `color="c"`. + - view_all - a flag indicating whether to show markers for all classes or not. + +- **Example use:** + - Below +- **Note**: used in Jupyter notebook. + +**Input image** + +![ori_im](img/documentation_images/visualize_click_count/count_img.jpg) + +**Mask of automated detected objects** +Note: for how to get this mask, refer to function `pcv.detect_descs`. +![count_im](img/documentation_images/visualize_click_count/count_mask.png) + + +```python +# include the line of code below to allow interactive activities +%matplotlib notebook + +from plantcv import plantcv as pcv +# initialization +counter = pcv.visualize.ClickCount(img) +# import coordinates (if available) +counter.import_coords(coords, label="total") +# view "total" class +counter.view(label="total", color="c", view_all=True) +# view "c1" class +counter.view(label="c1", color="r", view_all=True) +``` + +**View markers for `total` class** + +![total_mask](img/documentation_images/visualize_click_count/with_totalmask.png) + +(When interactivity is enabled, you can left click to add markers for the class, or right click to remove markers) + +**View markers for `c1` class** + +![c1_mask](img/documentation_images/visualize_click_count/with_clickc1.png) +**Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/master/plantcv/plantcv/visualize/click_count.py) From a1ea6791a006f5d53bebb9c19bbc03cec90d207e Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Fri, 23 Apr 2021 16:07:50 -0500 Subject: [PATCH 018/178] add documentation images --- .../visualize_click_count/count_img.jpg | Bin 0 -> 129709 bytes .../visualize_click_count/count_mask.png | Bin 0 -> 15772 bytes .../visualize_click_count/with_clickc1.png | Bin 0 -> 198324 bytes .../visualize_click_count/with_totalmask.png | Bin 0 -> 192838 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/img/documentation_images/visualize_click_count/count_img.jpg create mode 100644 docs/img/documentation_images/visualize_click_count/count_mask.png create mode 100644 docs/img/documentation_images/visualize_click_count/with_clickc1.png create mode 100644 docs/img/documentation_images/visualize_click_count/with_totalmask.png diff --git a/docs/img/documentation_images/visualize_click_count/count_img.jpg b/docs/img/documentation_images/visualize_click_count/count_img.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0153f7f22346a9b0ae4967fe223f760fa62a6884 GIT binary patch literal 129709 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 O^`*q*MeBqOg8o0fQ%(E; literal 0 HcmV?d00001 diff --git a/docs/img/documentation_images/visualize_click_count/with_clickc1.png b/docs/img/documentation_images/visualize_click_count/with_clickc1.png new file mode 100644 index 0000000000000000000000000000000000000000..0f86e0223c0cdc493d92292a6ffd94513b688e6f GIT binary patch literal 198324 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

-MM5!>BQiij?LLpBZJ5r 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 z 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

  • JFizA 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%0Dk#PnfOP#taVmg&o= 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�~Kkbc@*^jwIe)2NdNn!m=JUBM4MgkkgK; zD-717PmP52i3kFp3{)`uuD~EHuFQ%-K**e<(D8Nm&es=@U#XwF^_JRLWTlGjN0t-> zqd?j>lB8GRRNxHrww_&KWN5m>*AhF7`$=hyaYX%q$4T(yKSx$;7`0Va%UoSvs1<~C zj-BUSebe<9&s?rwe)n7I$roOzqZ2!7j`UX70F|loMjRJ#Hkkc?? zbfukmX))~6G$QcG2Xpuv^Kvj0HLchF)4hDeAvoPC+KU=99am&tgub~GU$XpMb8?_A$CiDx3k&%y}@@jf)Ko(+9 z)Bd(@N=!e~zx}Cd=o;KfKW#ZTypblzd2@2$1`@F&5(gZT`JkDkXKfGqa+UA458fTy zApd>nSth+1`NfG-jt2O(f9~;h7#$p;&b;%9$h(_W_t2MV!|*5ehH4@x?0nA`;j~}d ziOfQGXoi^N72LERDcq5vcI$Qh|E4;SCNd275TXzx{8jj1nF_#l*c&EoY=jK z?U0K0T!|6BP&Fe4mCv*Qy(^GL+m3=VBIziwDTQ4;1NpAe%k}k>=j+{vcGuE^qY#9J z!}g4@9ZAu1{xm8vEVOMt+aCo;VHJN+Ohn#A3d`u%!|$9QU<#uy_V26VM|Rb(KJ`Ur zQ*WiTYsFL_?F86H7yIlb=e@czU;pO0@6;WvO6uat za|c*G#6W?woBCP0yje?&7wXuR8yxI6P>XapMoMS^I&t|J#mOZ;Q{HMR zD1Ufi$RdA&B-R-v37xd2tnJZBw`mx?2z4W$7N@;dM*Lg9>U7$pUyZJ5w}8i+Mm9Ph zAZVYy1xS0>Mp5GK7use-;G9*LbPK%*akv*SA!r+k&>A|>i@tldo(>v>T6ZBj(h^w~ zmNp8*N^l=KP_LczZ5=Vk`Ep09h@m`^kI8$jHTK!aWA%Tss>lf4!*=BqYxWL7`J9jf7cc0duqEAEVJu?Zj|}017^8tra8MPNXAkLPm@ypO?rh zUSQYtB!?Bw5*-h8k04kWSCrf1E7~qz%uFWyc=!$H>1_JhVIpartSh&Dh%S2+GKYzp z5?SEYL>rEP!Vmr%74Sk1n7F0{dhZeDPLjqE(uBF#Ys^g?-F>7^z48)Kq;Up94C7^b zChF+d<)TjJ=Ri0W69sG=n9Ok+Jt`8?z#bh1Nejf73r03R^WC2haBbHkAB^ppU{r7? zPtf}hPd#35*?%a}jn8QBC;cHkRhH(kZB!w*#n!jR!d=_FNhi87jpws7aXxq8_WDOB zU*H+;RbOEf+0S&3)dz2yu0K6>qCR-wKwScsQCjJXVl}8+RM+U*9J!{_h*3q^!U-90 zfdZ)suNm#?c!H;+4~Y!Dn(>1t8Xt^|oXOl4pVtU1h8Ugwk8gQS4i8~x~59G)~oYrp*3j_q1E0P`)^m04loo`D(l z#XMbjrxUoCyVnqlMx2h>34Gik$rojL90SYMkd>q*13XH1e+A>Fr^I9L7;W^#M@XL2i1)$S?hHD=bp!nt<51 z23pt^ETIy9Wz%O#cqGT!*+o_=jT5;-UJSKE?=_5~aIO%|j&Rg<6jgz;9q{x+a8z2v zPvJ|{W|VD54yhDCafb$6MOyfepz*z>w}6Z?HzA}d;T#Emg?t|dYL;cYK0b3NPd}S? z@;no~^p(6geQA_j`DI%OEuHb#1iTL=(>W>7V!oZa@8jOo(L3^eKdGV5K;t8`JTv_H zPuD*m{*`)Ua)xI$DzstzJpLuPATAwtmt&aMtXXqIcDWVgC5zTKb=#fu=L>- z`2ueGlMcjhsqIy1^NZn^Gz&bYQ)vwzYOpl!8Cfd#iImeK5Gg~g4|pWTwdxW|?4P4Z02X4PhH}BEA|Q8lCM=Es(0f<>)aqmA~T0 zeRQfxnY5BG+BjayJEMGcOwK3PpzK&jvDwL*%ECjqF7*Q2<~| z?zZEEj8!~h`>h9#aI8h!JD&7{m}z8qSI3juQ=p@W;?0-WqW)&AEIp1AI>)oXPIiaj zJBNqs(HEYse|q^^9eMC?)zqcaHNo{h_L8h|yftwvhF^mxsZ>4;5$#A;(T)*B$)o(z z+euNC#Czba^8h&RCJRO_%KF@pyh?66cY&j(M@P z{l=)EdLNoJg9%mAcCg_Zw?YSLA(b_e zwiSx=nF3sklMzOHOC|24B7I4jjEF$ni}J#1(n)xvpzA=<%rGJdC_MZ-Ezr$(OrAfsTf)^ z>f@U{Q^rGYD-+$~jd~zdj<0Ib{^DTobP3=j80tdKdr00aKOOD;-n-hj9 zpQAV^hZNFCTQw&h(p*QM$$Z;EZr!J|=*B@{U*=gL8jHw{Wdnn~`Dv>^j1Z1T`WryV zx8N-B_D8#t#YtEr+RzWli@tC}DU=_|b(^0Hrkx(rBXlSIh`fobKNN@NmTS1$k5tH< z5;VeR(N^QT4M{hokTUdq4Nvfx`8$<6qG_#z43Ar1raZJ{- z&Ad7~V45d30UV``savYpZCmCpow>JlF@P&al%X9*17uW81yx zhRa`Ve4QX`Xn27AR*XJKYpX||Mj)J9G8lcaE_IX{X=~l~_+iTh0_s3Jf-Hdwk1gjt z%6j;s8Icx9ILcAh{cCi{oS@7SUjCGKd8EGP>_6JDe_?Ff6)2{R>z!QiqMY!fy>Vow zuDww&dVLdLGF(G}x0fKzEAt)hOFv0xjO2ex@&mgl%L6FG{5f**8>A>k*d;wc3d%F1 z8Z$7i2w^`leWOX zUyE)qmVCBFdvVG{nCveC#%HGRYK_%Z+egiTyB6FszF6i-if0Uma)}jEyEuAcjnz!H zp92R`1aWLDkZ4mQkHK=usaQk3WrdBBj4ZX2oirhR!2uL2JLc)dnb8@FzvW9X{!rqA$!dq%{DgmHTCs?pN+P3=%M&P0C5 zOHWZ2YWXYT^2b8rD=(xS*PsD6t(-ER$ClMl%oBL#8M~(<7jn%2B`Y|&s=UR8f}^-f zdku7(FOx@YgsnWbm$=xse8C2V=Z!aUY;mr(O54CLDMCAm%Afte=J@$!Uphx+D>ULh zA3`gyX&W=r>GY{gWqSZ}u|e?B<7ohlGTK~Q2X|!By1snIl6;91OvAbSy8dfDy{=8R z#`Ln-hF!&T;?P7pv3 zR6oX9=uHJ=B!jhRd1yLu#08F-EQf{;&He6tm0WH3Gh45M#vs|RhPwlwk(c$MbN)g@ zadDA?b!k2TWLy1TJq1jQa`CfD;XIAM0i3OpA*p| zJu!?+rcmt>SX>xFEq)u#?O&LjP<1p2NswU;Sa#!)2=W5l0eWHKe@kI_8fz z+u^EPQqu?Y35^05uqlHWGmktOVe;FBX3D1B2Y($h zau_)a-6%sHX=J~F0bV2p22_nc7&YO9w+4&!3S98TKdv{NgoD4_RYsvD#^2G8I^>_w zzzN<>O;JnuQr~6r7HaL;X8uBZMq!b;Mi*O&J+d!@A#e}93>IucKJh=c$SzF1(O2V{ zkRogFS6R#mBftxnKfEWc3GbMLZKG{RN>qb3mXJPeo~}7&9Y^X#PFr8%rQAw8C-mD6 zeR<3)a!zx=rN>pPO#7#88AjzsD`iS2r>v)@wO{v_J^olPJ>R+yk#P*xdC3s3`M2dP zMBXw@xFGBnR$G;Pm-sR#w2=9L;}<8P6c7cHdP5xMHpEQlm4yrS%-PdK?`uqadT5)L z&uHBfo=hyUOq0XcuC8CI-{RP5k6V};!r(Knv3+EF?dZE+x19Lv`qI#m`isNwt8Euf z)O*(F>d{;8B*ipSC#F}JL)k+bYo)Ge;c0US2lgT>Y@WVwyhc~PR=&SwmV z0>)3|Ps^1`ToE1{vyJQ@&?vVSQwD=(^ynmZ1Q%nilC>1a^}1bJ(B~Sd93eYIMA_f7 zqu#M+xW0CJx+WmVHivC7nzUu3G4wbJ=7uuLL22ULjJUXMv=@VeOz3Q~a!DBJD5D?x zkI@il`xiIp?E(Vx-i}&SVm)M|rUp!-X4{tcMZ1x~rrd3$WxJH1Ps=F>ug%GHc0cFn z8;g-%b>EO}_K|zG=U!-}!Iqcyo8{yh3W@GZgT|kwvsglS;HTfMYku4I)EWD4&P!g& zC#52i!5sjQHXTeX-O@cukn(FkY6YWq+8t#H-REC^fglWhanf0X4xXlOGdiN}A37_l z`GV*6)6ADf55WeG@Jaka1E7?b8dePQCL{dzEE7i&f+#YyMZNslhINq#0}<Bt>=Cz9aR%2ae_-y%-FHzgO2rRDYh;S-WwBbDaMzla2P;q$z?x z8HfGmWi?n??uYo8TIkM1Jc6a5AtXkv3J1iZoW@alMZ$OoCZkTS4IrRb*ibmh@?>{b zuR+%s3d+R)Lt`hy(=XQ)U`00)A4AJ$zAMxk8->u60B>AI(EwyXo4-u02rEAF_XOrDJuqz8&@Y`n`sI-I%ZP5hnMC(*!`-mAu^H2ov zZkXaQ)kr(Etlf43j~E8i7+mgv!#gyRp1x8qmMML{R)`NjtfSJk@{(G(P+zV|Ya50S zQCMLLSc%j(G1&b+K9VwyM&j0yKMk7aBiWYy`KGb;)Z9D)k+w8K83C}jB%?0mA*a7e zf6D`bNz~Q=uxhB}Q|>}Dei9W!kJoezc$KzcR?eN}(8uYO3pLp@#)e2*i(|Fr29$Ng zQrXFBBAt%}48C4>iM@2hcv%LVe2HFoV2%a>QDC5)yEax_+l^dEe#<_{!&820tIR z6>%2}WI2vL7zu)gQle=%?X2-ZNst9OF2_3DPoM?du_x%HOXb@#JgQk99Oa6c50|IQ zM|q+#wpCxDEAKjvbP%S&$>@*r%S{6+=wOeQbH1}>2yDTZcr>0^e3Lm*mkGbtwlp*? z8+_C$%L=!BT0>k7_qYaR(!}Fdyw~Q+gtjAtsjEEamm;n2oHjtQjFBa8^fISB9u8?&Z)jHVu5k#h;HM(NZ$KUQzMaj719 z>|fV+-}aHZHo1!pVZF?Wm`3CI8Wa8XFVCK@C7#@Og-GfX?0)`e&(n49;^q2hhdx|i zT-{k8y7X71uI6bK<&RM_&M*q;s)zRMs+TWtqysp(N=JorF6P|gJegRY+rg-5T|t+j z3X5-iSF9V4{Tvbqb#%puNr&P~VN!vcVp_i`1O-yBrLda8A=_6oOG|YV$8fuR7?P#F zd~goK=*GwNW`#>&(qQSJNQV&N*s^7l+TnAe!%k#ve;S}RH(@j<+(3@e6R9?G;Mo$; z;O-qGb#4~N97m`*3(}UR9Bq)kQL%MunlN#+E!%XWI;Wp{CG?^eWlh;Mqojgq)GSNA zj&p63rVWRT5O4sA##7yhRb@<5GzYx4Y5TbdgaQYoTn2xZaj;s)QC+J8O;B0`2jSQK zH`#8A?o=F!2R3Uiq%PP39c|OTPEzz6|D891w=xF2?MxYPDP|&3zffMol6#ak<;{v=BDy1V`X;#E`(pj>Lnq zWHiLz?}_$l(2T&-7uP2^( zq8@$pQ3%F#5-XrS`N>aaRN}oeXU^0kk38bXy6?XG^4-stT|+3o{`Ifd+W`4>(~?eV>G;F1mZze7KQ;0_YHRk^c7%OxAyP=;r$HqwlSsWu5!oirTG@5M%y9Z;Vfl)wX zB&`L16y8+hUk;Ru68X6ay2@iD*UI2jaDdAgt4W?|avolJ6olu0iK7O#7X{I=i=Ol| zVp`WwJ1+!nR8i9+Q40)EQ)F=vw~(OG528d-Gji?F({zd$QOWr=h?CTot=bnH1aGfP zI}KT*e^MWG+Y~(zKzioHm{)UEi#X;yHQ~0I7zAO5I*jOSaD!{ZY-H@hU@r0?CVA&^ z9*`5-Kv!zEpn#5oRO`FDvys)nST46ssH~z9K3>hK*iZW$@>m~O!c!d)bV_w^YIuQZ z2)_TH#Jzc}U*~z>`SPy3D=#;Zyd))2617;XBU^TDS&`$^u3JmBnzl~I$r$M*otXl~ zV2~ogU^2h}^UwU#KWfzI6oC;;>_OyZ+KjW*truCgY)7$WS^G*+T*RHcT<*%da6X^! zbAGhrIx`ADL5KX^-}#;MzR&(V@AEF_Jt>{Ke(GidPv%6D}BFg`xT>h2-0`AoB*;jl4OB=g?4km-3>GQD2kigrjUXa~H<` zF6inPvf=K<1Fjy#R^&U|bwG0xBpMpO1(QYr{e%>+nA9K-Y4zL2*2F+wq*2-X(Qob9 zXIRzW0FeG7jwUj5e8vtgIV!-nKGKgb`9mAdH*ztNO3$d1hu76YO87^-q7L(%s6cR~ zw{1g1QsAZR+eJZruaX&Q4HUksv9s~caG@1)UV03T(B%E_4=u)*Y`bqje2Wg*decW4 zOl$l1?@v!-Bil?2(~J1s-~HY8Tfg;NZIX5HHVk7t7x32|1|)kalam|T=mg{AU-)P3 zYljcEKOs{&diEsm3pW@qvq{|%UY69~uIK4rh2o^&2#wbmW4G=XvwLDk+kMqWc0y&e zn|q8jsS^Si0@hT_X0XJJdm>mj!V-m%DTz6x_)Q7p0QuY?sZRWjFnXbEbm=y zJI}n)zOw!5cHf3A?YFL(Y>y7@ZU1-wuJ$kHzu7*%`F#7{?vJ%y@LEQqF}YvT8o^?& z?VQ*N-7mGLPS3S#HnNPD{h3#p8yV&KkNJ!Am~dpre&bNkTQ+Bi)FEsj3{o0ZEiTYd z4bU}Kj?v&adBQyz4m?9QMPg@WEa^c2*C*O}IWAA-FSHS!qV~!7TJ9+fibJnKFPSS8 z1_amAfFb`_e4L)b;sACgU9C(t_de3#(~uab{728wtw8FHY0lNSp*cM}hSDZj);mu_ z%_EgG4nyq(y}eV?$T>suq0RXl?Zb!rvRyd%*JUvQbb-@tE^1gnX1W9YCHt2F2{WV2 zEgenWz?<}vP`Ghp#@m&l3kBfUp)FzwHK~>OD-6iVU^7=i9C*NWht*(~7~rZex0TRs z4UPs*8yDk*ue06G!=z`}=A@r(Qd}7(Jw?ATUHqYLxn=|CaF)>;+Kdg+J3px!+JtWc zrVi5c$~m}Ayr3t;6yP@F4!zQb99HPoO|V_dlZ*=7zHO?_GPf6<;4C%-CRz1tg`8;< z^5L1n@IU(FUxJeI<%;t=s>}eW{X$u;yX-lKe0s$leghA<@M^;^bG0x;2qqJJ;}0%l zt{^U-((Rmsa#hY1pltxs8{=<=!O}obp2WmMUAm=wy|2T%TnB$>Q7-Dua$XRIByi5y z0pI*U$1w)DbGLyc3lz$;TFO0%fc6F+bqzf}s2n`XMn`1sBfUE6rI*+Fb(|*9%N_ZYQ*Cardx`Ck;bi74}NBEJkY3>eCcR!i>a~r4I z7YKN-(*=BVd8+*?FL-(I9M3we-PFz=V>#{_hJzT-H=zfbz<@$%1YX~ztJ?qk(FfY! z|MRc40}Ov{VRNcwUTbQv;R1rJ#{d9807*naRKn6MPD^7bPh>3y|5T68HAJ1)hQtf} z4P1?tpPa`)Jx$Q!n_^}@4l`(PWtifk0(dU6#rpfN-5W=D}PW`7gdhn0i$srd8)5mw zg5{KEh{Nn#<%%=2dWXgGu%dww{@p3nVJ9KE>O~~go8t<00wl^AWIW?u5DFV1B=7+v z_ibdLi(=Li_9*yf*uuu))c{hzIyEJnN2q<)upjROIM&R!J%l0Z9>$->8H-FN9UZGD zxbN&#Ozbz~ofBodSwmh`lZr*w=Z@Zj>N3a-J!w*fF^AEtua8$VML5)&||UL1uokmQhz34?B&WD49hoFp7&fozne6e=SwdtR?v7CuR38Z0hz(_ypT zK6IDoA8A+4C&(eHJ8r)tl}?;E(LVE;&tyXVnP;A9_uO+&8jzcBzPWw+)1PjyzWQqW z;0HgLvKod*Zw1#9&vEQwE%WwKf-)W#g8$8BZdqeZgnj|$$+S#sg&87`Ed`* zePC^w2i6p6`cQm3ToG|_Mc$kjI>$C6td9!}jWrKIQZ|7T7@5Qeh|4N{unk1WRals3 zoB(6i5i>?Ct=zN__cEO6?*vT<2YzUcFgfG8JL9hBO@``R?YzoEo@=MR-sV|QFw4E8 zhaYJ(v-^-BOH3)Bc^0l4IQp0iP_|-JJ^m4$_q?VIOeb{A%G{9-b47q4D%OK*D-{TP|H ziMY@!y%2iG+gaM&M5lcX^8D7OkwNnFUr zYdTKH6Y0SKTTtP82s0RlnQ09eB`6-e`1++-Sn&vPK1 zJ_Ve{KswY!B_+elR}8agRkvBipI)JLFVylMjLUQSST-E_I}CxerXA#nHS(8QQ5YoXjs;-h1E` zW)c^Ba>&%)Y0&W}^D_LFQj>43XG3nUXN_To1vU}&K}2!ev#ix+`iOWI@ndc4#~y0i znehH4<_w-dKzrGYZW|e$lg&sY!8T*(Y+RSIP`jGotrZLkS_KN*kJ+W$SgQ~fp)eJo zy%E0QHcAjKicw*qN-^LV1Ju(P+;k5twhO#zPh;SHSqV9-8#7He7zgC1VXkpZzaeLo`!LSjLbD3Z!4{P*>;=~$b(EG^;t!v~gic{dZDit^WyM>3S_Twy?L1WW5NP}9~-9J)6SIxFrsURY$;-y&?0zYpc8HcJoJd#ADCF~dp-BO!F}tdm!U2L z%lqOmMsbersZEU40@?k5xin@ZKlxd{^6z z5&p$%FSk#>@_akNa~y65?l9F)GDVsI;AzB4vpW9Mu>%ch)jVdHOPr_;LPN)n;B!>0q(58x-; z5}(FwV{53g0&C1%Rio^Iop&cdpS?lP=XrgrUG@w?1v(m{x0sb<@MK-yqm_*OY98GD zcwvu zu05CFmnfAaN_k=9*~W$v7wOJi#q&>x&&{^aGADKRTx(DK@agtriapMIV5$KER(h3? zlSbHVxojOGzyhr_h*G1d?8SQ)9q13>SpkpBx-(oOTPo0GL#{pLN=02PPE{muaYMfq zDz@V;1DQMWH~6FCCY*Ibef(xip z_mCbm+ye`AR+RI+co&B<*0$`qhz}aNM$<%DIU28=@K3KKJOdcMO^h-kL!J6B?Zzd2 zxJNZbuizo(zwr?x`l(t`hFo*>TDJ<`fA!i=RmrIWn4%}M^{H>Q4Li2AwVm7B)<>V> zB|VNqBTTYZjYeT8fGGa5@d&#VrQ-dlPsGh19FQ-r@Vg8#322Ev2qh)ne0GcIkAR}PW7puG$+8H+9GE?xK9^!H`&f;ci4j+y3Y{|mPnRfaD zo9*!smqt}{1UOk}u@)8l#JN0^U#~elrc;;E#%Sc%`_|Gh_*Y9nxfI93&EUx=q7B%m z8UDCL1EMP_LX9+M(u?zE{=_TF4!D;X_f={z9@rYCU0|u{gLm&aHe0Z{1l5AM%or8**A`mEsx*I?g2F3=Iinv zS%8JfMF0ta%Z3mg8n}FYfn|c=n=VC>Aq0xp zdr;?!(1~qO!80-e2cihS>E%ibxaHF$fsIpFx};If@Q5-12D)~ui3BBr&lpeVFJnhz zOwMK-5qVJ`;EK;k5J96JI;5fJw-1}HGJLDdlQ_^wD93&q$>Gt~j|`phlEN@c%e73M z4Xh@dnLpPa-T!p^ACEuXzC3fHeRkz^dyVDx_mKpp*JiL*R_d(#rvQ{m1OYE@E|@~0 z(Q=${7^qAtYTi+OXg}pcX%ysNitvFNVl4V{m3oN{mSK{;wxI81fqdoD_j8C#ak~<0 zkfr|WJB^5T3O*^G!Av(?b5}eIXOno98`} z7{ZtHJed!1WJCYf2CP6`V}}34nSa8ej`+MsQHPuzYRxj?%zbg)q?csP*rZ?ghUMjn zAN&QrNlkd+muv_OJsXEi9Q3jHC#6j^OsTu%r85`W>w9(~J~OovVBR;ZK(Y21shfqVKaDm* z6^@EWLJ=gx2nf(jM1w0VPF%0gSfmO|il{+xKN)Wqlrqf4eS-(LK~YlI$wcV{o|L&X zcnas5U^wSOCWd*9kb#6!)-W(Q9N`<`u4W~d+nK*{;Vi=nZpdeDDeZg$Kg>`GgW^;O zS$3!+Nrz=|lr((acW@mC%S(8GTf5w9Fya+1yh6z`=Oe?ccB6q_1eRm#fv)lL+irXs zNFdi_r!o@=;gow~_lOMThhS)Sqs-Ir80b5iHP8ee%2(zJ-2{J~*np>LqY|Es@8@lP z2irH8*P43#KeL7M`u6{9|0qL(6Nzb-tN<+_xkrbpFgM^$ob@B}m7YQ0RtRmXRfqEM z(M^S^|d*s`si z<6+Qxzq*)L*~W+GnVx^Dy|(}5w!jd@0Q_ltS_zxT&=~i%$Mzj)AG!4%ZQ{UzwuQGc z&g{9Z?PAf%1opFPc8o03v!;9v2T70mujD4am0>4?q3o6(wQ{rL%X0$QhWgc;V$b0P zKEg{+FTmE{Vi3yT-(4A)HkfLCqOjpFIsg!y$W>ZZgq0vs*m-A2hs*w&tGsGryX2n) z2B`Xa$yc=?BQl*NrOHcx@iKq3v&a%AOyy`({_@{JP z7kbTGToNCjpnPmnLf?WTEWZVC-xSDUn0lU8`O@U}_B&g5w12r{d)s`&M0fYCpb~Mz|hB5 zwr?4xD`M6y#~OLE5l{UjyF$*?gCxKrCobzVHc$qcY~K)i&BUY?bGCqv4?IoZ&VasB z0@t_+D8V71N(R);9K$ECojBZnd)vPDOQVN)f5=SxI|RTB8Fo{`>P~&Yrc>uH*)=v{rlMQbu`bxXur-i#12Jn zIRe}kdURv-Sik_d^UTJ&|IrQ=D z!j8%D_LUd+wRh~Ak|61(ktVe&-_j7ej}0;ZdUhqY36}&j8YslHe zPe0^;_bL0aREC0+x)x2bc~=qw69m|WvVr5o49lceeM?;E^V*d^!p4;c`{p2;FHR%4 z_c5=qX=4AZZNLAk%QbU#KE@ z$BNXw``~Dju~G}eZD%l9g#-;6V-2y`_yCCuHo!Lf942ulOi(B;~XjV~~PUHC1< zrz^(~U~z%Yft17xK1Cy^fuM@0ncJzVjdcq8&WL zOPn@OwQJDdCIYxLQjiZk*u%1}jla4^hgo$b-{5qM^BOl#C90KN%v94;Fff%@=PN-y zlBGNZ#_@oYj8mgQCM%BjYZeKlqM;GQ4zr~`WwJu4*bHp~(y=p4qYgOFTkJ4I`vRfN zh@XcKnU{e7DH4d=Mh4qab|o03M-m-LyN5EQSF&S_>&#z$?%9{yhwiumyBZ@|!3OaI z3G_Lqg>@&|=kEBmwtC(DZSMF}?Tc*1bB@I1>dA@r$V-QqI9+IWUUhwYogu4DQ#;xx zcJAWA-Ve0D$I$RubVEBM_0SL9G)}b`#o@~eit5r52EsI!$p+v{`D$CpR(Yi1qh$OE z@)f0iRT+NiEy)u4i>*Xv+LL``?I(o9Uae|lnoJse_JTtX(bitJHZ8pdcY~&lRcuoj z@#l^w?M5B=q3&fGv7LfZHoGpbAf{@sF1YPSNs|eibD(+9k{FO>$;KM&A;|^e$-}9U zP>d!vWkZ96ppluNydNGJ$XCj7f~~v2p!l}IfIfK5>Ph*9mGok*1z3ALfoP&9AYzVG z#zq5J-261*`6*sQC-g@hf{MSU=oSB?7T>l5d9g#N58?5U9a;Pe0-YQj#{?W+fqevM zFshbYGBrdEN?EhghW%yix|X0cu)3cXn;@Kb9V=MjP$R-mVZdrQX1U(SxZ+GPJ`BJC zMGQ7_hqh!S1{AY1jX?|HAU^>r%V~izNT8os&x9WteRV*nswni(DJSIDYE}btE!}7Soh&e@_xzG_x6qqRWN{WSDqb*K7vy_dnGLbm5VevVK zQX9It^||+aa2Taf7fCw@p+1mbj2lOB_1;~#-`kE)@8;pU(RS6uwl>GJB!&~DAaMAu z^JKKl=QyM^;Bs4F#htnaP@5>JfJ@#IkfdL|m(7GmqZ(!HMP?2PG}iMdJ~@|sK5l;< zS!M`Kn99~H)bjien?0BDekVg&wJd1jWVm^Y+V?TUUKwP$)otnuCxdK`OFg6g4ae?ehvCE;P6pm0{7wHU>!!V z-wMRQ0+0R>|EDWmvId5_l{9R;hO<;IJXsdGti$GA!~!5_rk*F#j$Gv59VfI=<4diu zGH!_8o;A9pzC*Om!=^z1hDMk=_<+6qvK{KgkIHTx8dLOX4CNnQq{Cj67O8BdG{f!` zRL-_eBw(Cnz4|cYxT;G*$h%50(_L6S$8yA3<`Q(i zXru&4X{1O=RcVWj0?il{K6q4`ccUcF_^%?|M_0jHLTni>>aIA)ytU{rVvC^D5}V*A#1{_u~PbYE$EC;Qs1>scj*k!wmd zZXZHIgV5tRaY*^&(DKCV7Y>|M&nn7<79qW`%lvR7oEY4o-snWR@J}WaM~U1tJ~$T- z{|vT46^9cid`Cbc0td47XUa$^*GPut9(v~5;^~t(g*^uw4C~M&fHy-?@G9{-u#NNd z*6Z3<8jp)Kq?XM>0Gv&EORKtYD-1JTwkDJdHNZV6?JPD8Kk;SU2f7GUAb=)^CyL+b z3>u_^0&z?Lai~*eR=TvF%bpWZ35?~C!YI90zzRiDzZykbjB7b z5F4TdhNBGEGiP#&W$%jy9c+CCeQHbF*)8CuqbJ*|Cthv;_?>sQZR69t$AvxBsN;lb z;Q|hN*}m=icHd3!V#kL~>{7j{oqq0H?W@l{#Zuol+CP5Bo$Nla)RuUt)8~mY&=HyX zgf=U|AajV8et-sE^3w~xXvUA~{KF<{YN7d2Bx`RZBzQcB9- zBqc%J>hS2qe#YTNh_677B`H%4a7<1ycJ(Y1;=0(N38yJ$EY^DBbWg475%`nx0M*~q zE5SCcANbJ&zhgtvSh`m)NkY_>I^(*u$FC`+-VM^)j=+-OAfHMmO2>IUt#~P83I9e> zUJLCtELFB>Icc}?gl}xFH-EXzvu4@OL;;8nJmT}!%B6p3MhI>kdwCwFXAr@u@I(zJ z^yjKa4`r6;%;F!pbjAND*|+V85y7NgJcl@3%6Q_!aRf+Mr|aLvO+;jvvinZp?H?yo z8%qnXf+ISG8ACydw8UQ5h)b2=(0+#_ogY6XgzDX`!~0V z4ZC@j?l|M!or2TdmKU9?3{k^LV+Os_A^ys=VaydG;5XMWpsk72Wl&^evXMx4OFp=PPJ>)BCMXimsUJD}JI>+Io&<@Xz{0aJqgQPvTQ>`$ zh{J9Od5wLrtgHU~pXHMLUZ&w#PC%l;z)~utFb2l!<|G{QysC zpLnAkK6{~!14MkC3rj%i+mS>qR0j$@BS|(>Q+NKz)8oK*?kE$Mav7Y;5`92V=6k49 zJ-mc5Du*QKGf-53RqN82d6!}~c+`>T%-ZGY0ulVK?K|2myIA)#j3s-UcxTINr_Zz( zPaSUm@SgXyv$Or}IP+%iNmUK0!%o1DTvpntm4)^&@0A&T^_e#LwdV&V+f~-8TPAacUC%%}UP0ff!!73x`GFZCmFS!E^iriWq6M#Z93>%lumHn= z>^46s#I?Luz%s&cm{dk67myi!02#Kj%nChiKM#!tr;y=II^ke76NElU3Y_@7g3;df zc^ek(pYvSvw?s+@^{JAHczq^_g$aD?*+8L#tQHJ5G1~B2RT)dO2@efA^IXOvjTIL7 zU{{Q+I3MOeFQ)NM91@4HN+zDI@%jnT+wiA>vDd7vh%5h6g+EvRfc&@bC{d^q#esn$ zs0k(nNh*nl1XBu_Rn)2s1<3#b*Qys_P}K2NrVgRfRURy%6^cd~>N(FC0NWr>^TEfv zW(Rx>PTmoxnGe5Keb5hw(n#!5Cs4#_pw#l<5`>v=4S8^#vIc*Z&*GhS7RP%p4dS+i z*6vz3((Y!p)_L$>WT~Os&bt+hWgV)y#6yOD2dIXsJpx0+*H|qVDVbpbBfD&}vPW9W zt-)^~<4R*}*B};F_~{;C(jVBAuS{8)6|FU4sE|M^UmAUQmG=sm$|srl<;I8d>BXoM zC!^i#iFpL%mS6=0@jym*2p_qtP!Divcxe=*8osDf^jnmFnZ&;w7}T2%srFoP)OgR! z7^52(+RTO1dJeMUqJbXzYegPEAs}|4?)^BtCojnW2@S3+gZ|zQu~~kMTrV=zmJAx0 zzS*(@DI8&ZK0H@^Ii77YqP?gGhgb+rBovD*0s$LD89;P!d9pDugfNLcI0B7uLJ)CC z%H_R}?|;7i(#_Yk3m5vajU`?c5CQQ=TGXljz>-$*p9kkJQQ&S8m_Pi}|E>M)cCOuV zkbtPRN84d3!(Nlv;~)}jh{cjcQpGPWeZ8as40KD}O5A3Ikhc~E~* zv~-Ho|0WFL^cMHIqW}5HGeQsx$|?)(@?LtQycC6JZHOCG@ko!xO4@Dy2^WZL^FM#; zNLyMukC+^*P&dHfy1YOn=W+@6$VK0fzVv|A2fsoWL{tx}Lr9G)dFWAZ<6*Z<1JI~( zJ_6WgwltX~b9J?b0R{~~p8-!YRUlsqTn9in8bkya;Ze@lagDu8kAVZ44B|MYM<%DF1vo0u+)h5+Lhv1|-#^OI z-Hqc{wHvP5%CiatCU{-OSS2Uf2zh&&O?CqirJ;P0%FG$GR^QtdKB8A^3ztOQ=C{J&uR3=^24V*X-nXStdc#~ z3}_>Rmw0AlwSDBS_h1)Hax)ibQexP*ffAQ{7eg`(B;9DcxIadNJ-GX(_LblHH|?6~ zn=nCE`qA)YdvIWz1l7`C?Ny$UA3!1xzI_&@Rsj)g9S&9^%1j#qW4;pvD;TRHVMtp7 z7}en_G|6>C$end4sv=wXtO6e8EeCV{Aj3 z{eanWWNI0vwi5ScAlvo8k5ZG~DzEW^C(lE4J*fDrd}@ngx52c&!$dYB@}La{5+qX= z_&%GXJ%&AbmG=@3-|CBcT#WsP(r#2B_ThpBbb##BK*Du~T!}qds?2Wz`w(s)&qa-I z<>hu{wz8Hu8Du0UiAA8XxV%$GIJAqvEqc?qC?xso{LIo<`l$GIrJ=rAcX2)OK|Siq%n_=rc5DMFT=NkWeYcAT}rA zlR+4mRX~C<*_I2a^V>S*Y}I%HU*l9^YE1{AA&KEQkp?XZAPFSEq^ymhD@rynJaJ~R zug&tC@JC&Oi-rOX9?2>Ud~Ux2Odn*E2A#%?Fa^O`M(V9{G*EEOl0wy}4T)}B!n+yi z(fs+#FR;bEWG(V_ot2r zO}S_$tIHuZtFyqPIHAul955DQfy-ZQR*M6cy8&&Y+ZIJ~1_1K&*caokt zhAgxh=ZVyP>^nnmG`jvv4+~hp_N8Q3Z)RV%O}DnceDPTO?(4_fz=@t<@w^rQHR9Lc8yA&ZncSP;eZp?fo_%;iAwM)wjU)o&6rt8IjgrEM zaTvF#FuNb6D{Vi1f{bGzu&J9dS#>}GjSLGY15)iC1~B#GRq+|b4SL?CyvCDEmcR#| z46#U)6m<@}hVVTVK*>RvAm~uKmO=yZs-f%*B6^8skQ$%6I z11?ANEOj}1<$mz-H_K+F8~#n$;^34vAIp zqrQltDe{&Nig-T6J6o>%#gDgjqbu#Z#}2h`GbF^*5><}gmkpnom8q^D3DQEnKc8o*+;Ne^eh{Gw( z(s?B{^3OGR^5wWR z&Mlb{F@iG~I6m9`rpO>pb`n7Qk*E3JU%dFE_VjtS5@#E0W^=hh9h8y4fR)F0ge_E; zQ%XbvHW=+y#9(tf^)&FcWU=6+s4m%QoI(yut+jo?_zsT!{11NEzW(B~?HfGEw-|>2 zU#gXpXc3i-QcQgbmnZ4gR2)6ZY@T7e7Y0pG*x`heAsO~-#18x->#*x`wFMh8* zx8s)fx%o@&hG!mYn^{@pS^}Lnd{A$h2N%-|l}Px@vlPM(OVx97|0q{Tnaw#FnO$U+ zsdx>n@RMGaa*43HrL#wd1TJjUBitH<0~}2la5=X}mWBhIJ^ONBH@E26&MvzrXV3DE za#oy?(W}o=sB(5=Mqn5`!D~R!4l>41C>#XtAG2!l!?#@5me|?&;Jf}wdyef*+(a>! zpsoT>W@(H8V^FGp2k@(WO6iBiU&+|L z!))9OsSR^?PVY3d z1fW>>sCVekFLkIci_%rMW3rHT>D-HUp5B(Ym7+2$nF~i52coh!u4vo*?kbuay#?a4RL9ekIo{XZo22g5e}<4X;5HgUB!s_sc9HjG}3PA=Blu-pPiJWMfBo zlj2U^0?50JFdpE6*WjRm$#Z%`o?w7BC^1r~(V=888By?SsOTv2SB6%yyw@a{`&?13 z4h#7Hl`>YYhesSHbpUL(q~}skG=fjSQZX2K_)_E!OYqOEFZ&-sbAnUoVxpEzadDAN zBKyzt^uLP{1{oea0l3j9_U5W~ka;DBSCmy@mX(CXOhFShyH?`z+@=2zRP9oM#xjn30l+sJC;`WAI% zhT$UjM>9Z2U-I6!C`j&E7Cg$=3tj_{@=;#-X%OX}4U!Fm8IG!SIk0*yF!~K=HcYa@ zsVe~a3nT-#wy(^CGIg>*CV`g<+_1jV>=nvpruj+op`Cb8K2SiQz4l={NR%7m_n<-a z1UW^1z)jDNYw3OAi$07gk|dSkNBu${Fw<5*ufF7nh6`)#F;=3d%?n6;p7Z75wH|#m zminNgD|*Pm+jhuPjsGcBvUnunHtFxb_9o63*^Z-3L&GXZSFi{g z;jNM_E;}LXJ>ekiQ6_+|e(d3PZ2P7*bpHeG{^y@)JDeQXm{g)cj~dAiRlZ(8aLAB^ z3Q{o|0M|01ilSZru34}7?hUFy2=^-(I^0$lH3O$QSK5x(e$Y1X!mQnF;okr3kD1z_ zj2;E62PR* zC!W{ZuCdK+Y?#-{($jLjL3`?nr)NW^LhlXx)wr{Gxu-I61PVowxpmM}En8Gx@~<;m*5L)t zpcyT-^DI#K?vD4ihhF(?`^3IKXfN))za8PBQu;ZRF{49g$WC6903hpRpV9-Z`udtH z-^>pAx2|st3bZZXLdSi3sLZpugtiWYm1EY(DRfC6v=WTWtP^+vnq&uIyF;yPmJonN z9g~(!aLW&MFA|gnS^g~VN?1#`vDHRg&1PK#$106mHP$v{TrOp{H)dzr9T!iuO>B*S z=$1R%2gionjT^2;|H8mt!6Agu0d4UgGX=GzV&gpfTJPZga^*yg)Is>Lb5vJ5V=N@?`D6;T} z@>Z^t@uTAO|87`pE%t$JsCV&qb`7_bEqLm&8b2;k#)K*lhf+)5F)-^t++fth<e_7!*Pg8lJ-_Tq4tB6Fr8zum_H)6=5=LF*VJ)b2`Th zGwnRv%rCO+EsC_&2X=-4)KGr#g;CUoX$EZ%9l_Nm8jK1OJ5J6XMADG%vd+gp4a1Dr zu6iGt(G0kbz3=1gu4mYyoM3$L{txg<+eH+QGvOpUk&T&E4d6Pao5y7lOzwq%&=e-2 zsCr=4<@Sl}Or(-|$hP$M4T7699XmY{jB^>G%(8K#JMt!L(Ge{V&hCV-3R@bp9WdpP zAjYXO$`a5RwT-kSYwV??8ma<2aP3H_i_@5prcHy$n$QQZUpsEM6KIe3X0rg29$h-QE-)J}8@u~I?coEqk&SZ0_ z4d{?*B&kuoTTY-c!!JIOtmDyGlC4@KBoBsx&8LxT2nH=DgSS^A{cr)T+LKhd z8?6~}>{7nurwkgu=UlWlUgA(tk)+w8clc(t&jh&k&(5_;b|tQiyb`G_3Nf)rqfdQx zh@tqt8ha_@i1vG<*R&rHNVb0YU$_5wj+dXIBA1YZ40O;**_7wI-k)FFS1%qFRX@wdfJ=z?IjAQ(Tg3ZBd;k3Zsvg3 z4rW&E>44Ff*}SmegE>qSrD0wNynb+$hNT+y&;jk*mC*8B5we9^es~3Zeqz5|>v*h2 z4Eoe5{A(*oiYQ^3$X4Ki0j)_Yp-Wges13QVH_*z}N9;`gD4#7?(r}BnA(ceSQTZIm5z0qM&-!u z?{7CAe7xQ9^mp3a-do#2456QF3ZST>8iyE-tv`xTu(sOjt|FNpt4IFT$0rVb`A`j%bgvW{oMZU~uFRh$y=jajS0XyU;Z#ACGw^=h~ z6k2$u&H`_l6tAm?${`7$(4EHBjE|B&Sd|)-Gmxv7Gz!X;ATmy4J$>_Sc?~Npg)g6r z5s$Db1(}AN&MA<;PE`y8SzF+4HP|Atkwjz2+Rl(cB)u49HqNsa8?XRZK&ZcwO+3-Q zaqDjO(&Ak!=*6wUlYz+_##8XoCD`*2rq`lP0BTPPAau~MMIK#!@hME~Ba_&cgBQ&u zTjz*}Ye{B$@_>Gap#qZP(TVqB#NP9gfe@RKC(fMr*+8%Pg~PA6CB!^V(8(4Uk%M;U z0dt%vpH+0SfnmbavrBDpXhVDNwbSkWKlszO_`;Xk^s|q#63tgMml-1-FsM7~YqMnA z60IF57e%iO(s*b;P$aF|y2FRj3pSO;$6j4{p*--kgVH0_{Doevr;HLH<6FWn+shDX z=WDqS{{nQGa^EGkYIhDYjW{$EL`*E+8aj(sZb+iAp>v2~KYb^AT6qI~DO>*JTX|X8 zq`*1Q(4hV`Mh%p*@^Lw?AfsLblVC3)mc5MJ-_ob%v|(g|6oOSgl_&C!UF3)5C=tO? zE3{KCExbQ~K$6x?^X%^2zmYn3CFvBPC;L(IKK7tJODG|Et-?G#UjXi&pqB`U^ z1okdmvG0m#R}9?W3{Y6=87+owmvnM)qCIo`xi&elmFFjh+o?HMHdF z&4$;A1tl;MzeXA7IvzlJ8}9lT_XgS$*X9v$axaaRBMw2}H#{lx8v6u>LlNnvKnGAu z+$d<(_c3T(l;t}Hl!}z}`u&53iGE!dd!}b6&s%lpI>PAM_D; zWPMHw_b)SqgdAK#Ek5hi*t1Sna=_;~qb%~vdUzbq#z;9Rajq5)&XPewi}&k&;k+f1 z-rO)7RrdQJ9`KiWCrg|*ve2O+i%*QIMSB4%aGszU_Qq+{EVL}rCKLsa5TL!x3!f6*=ST{B3bPj@!R1U7PDv1Z${I@1o! zoNu>GY-(rc*jB`WcoM6~5RFLeDta8}x=(vQc5Hw9uWrA!ZJS@;cD(mP?e^R6ZaZ$c zH4hmsG34cvaGwFGfCOze6w)1Da)Jt>U8bPHM8$JibQ8e=5+Bc%MV>_gd%7!JGnui3 zIyK{W2t}=?rw6TSv4*acvA=9|^gRO~xQ)H`Kx01+v3Bq9(EltaT@I7^O=6OrAC!TI zeisaUtCTS14dCj#0+&6?NS~RytE)VuMgO~GEI|%Vif1^Szj-78-|6R>A6E~yK4=P$ z;t&q?O!Abe3WpBi*=S0V0bYpq79~kcWn)sIT}c-8ylIbI9~Q0+nRSqh_aPznW&@!Q z#x`X&{A-8eSDx6XWq>C+5)s_upfphU4b2jjKVE-pj!rXg4e!S>>&UL}^xLk)?`4&7i0h z=5NrdbkhP4lx1U-Q;p2l9V64=yq^8KES3V?=Pb5BaJ6nSHtwy`)5*9n#_8C#`JhD@ zl#3BL)HOh`(jb?8SO|d0dSU=LFyv?;;?-C{UWNe1S{Y_Qms1VLm()zMJ&J42Fg~&w zhY=RQv2Aio+kbK&&z~%^GjKHmHVkrPkayT*gh1Ml(@n4|@OgsOBwh7s9?;uFV=~2v z+puHy7-3&lk?2V9EUy|n=NXeVAP}s4x3@BNTtD+joJU68UmJ`ptE3!+*HvIRIjPPo z(=x~iTZh$I)U(4w%3Z<0fxSKhbOMdpr;Ub~%9)EU^{gY-wPdVHEP%}Lq=7~^lgUGb~jfYZsU!oVflM z+O>E5GB23g&aSmDubq>GPM9M2NhA$fz%S8@WvhfW7=WirC7x^aAuwWoP z&K}PxAaw0eKf!ivpd;5FUVM*jXCi-KNI_yz&&CszxDA>NnF~jQ3UA>cveV*a&uv#+ z2Csb~!Qj$QJi0)SG7eKgwYGNU*RgG{l4ie~FSg9U#DjuWm%ab!0MD55c|M3KPKIpQE{Fo6Xn zYgj5a^-ydSYI)7ia-A~N3r`#uVFWTAHU?*df^vapc4nZhpvdjcQFRQHOyQ+V9gyWl z8n?!fpvv`-79)puei+PbI5n?O1TO{}xi2X#4d$?@oY8Z;dO;d4%4_GBC~{ z*HepbRkG|fXed&pIm~Alrgq>P{eB>xE%JLoXONe)vXBI%(If^2HL5L`K zTOb<_%RjmGUF{4B);d->t@2jPMf6k;(%QgEsm_8V zW0Ra}@8Ip{)dN`)N>Is=N%&P}NK5@;BU!qf00x-uzIF8v8^UBnX>hf1@N|AfRk&GMm7>&JZgH_qTCevvWg(_vJ31f)z`TDuk^@|3n1 zAO(cW;XQ8fF?qx{yzK9{;3%oR1%TfaNEjZQBZig%&?Di-4;HL5*c}Q(NQe7bjmem z^);Coew3gg0$oL_OIs>?Mm+-%u}(n4b9ua(OL0=x?fZU+>QwCdm z#?Wp#uryLq9uU18Op z765hX$k^dO960hr5;&>btDKpiK|#ut4G5kWO+} z8XgLTH&-PkD?UrP?S}{6Xdl0M4?~Vy^Aaw1+m+r7@qsfN z>CxzDi?R9ehA&{B34a8#Uhw7wIfsIJsvwv`EmFS10*>AH^*1MqlRIty_1s_g`~! zd&ibtyozKA?0n55de%-Z~-y(B3*E( zjU1LSaR{GXaMqi2HiNTUk@GuCYLyYgaq7}%Kr&whC7D3CmlhxH=!TapWYF%MU=A(z zqPkOv(}C4>Yz7(Tp7p#=P_Lmc@53{_3Hn@+;(g&LfsmFzF2Phj(IYg8#U#PiduLaM zdCA!3c64?z3m?>zJY}ARze6CNUs$3cKiTfyy}O;5JFnQN%UlP6&DaE4QW`ouvvIZ2 z@TMGRK}Ld@3~IABG*!}imFZI!z}1_>50r^8`RGHqA3wWBCc|Oq1>4p3Y;dB3;4Xq( zsbv5+L$>$5uAjLDfJj{yb#PUi_P2AHX*gp8pUm?TN${3QAb9^Q}IRmB% zh33NbF2Si$3#tl-Lrwxx^*LP0YDurc1z#Ena?>U)iN5sV;8NgzI5Jo2eQks)y0vph z%FD7Y^vOGgC{v4VxDGGr{lI7S2$7j~ATdEeePGDBiArx+w69zIV8PHNd@H4%SGYhe zd*>!wvhBmR&arxzetX$L%1Ql|JxU^17Ur9WhQzBS){%5r6p6996X^Qk6?R&^Kntpa zAQiorLxm=28t7G;lC&$Dz^0P}Q~xxe#}EvpmZM&@twDfEd+1pde<0aC+< z1}iFpCe6!$9K0ERp%xjZ0zEsL-kJrUbx-oCG%b<189o#!#Wn`^k2l zIPrSu#Rx>422D$a1}0K#SVy{5p~FFZ3ll0eBx!Ld#(9oKoL%SKzyoL(aQ5T$P|mWK z(h^T<_g(+Kc4p_*?b^TmYCF7bdwY##p%)pxvWKBlT>$1UY}>(*7If#SbgnyW(aA7l z-0&B|a0DBJAp*JFzrllk;|#sbu(~+LeNmrj@c?yC&;B$;v62m78&GR0DK_Tz-5n7ZtMLRG?IbCP4yXB zBRd<4BxhQz$xx`^T8uluLWc^r4uUuQ_2EZ0OeP#Qz+ThA^V>rCtxZ~9Fug|FAKF6i zFqxBA%B7bE9mt?JF`kEwk(o3&pOMiFAtnx#&j*8({zB>KTtmh+R|HQq2sTA>sL%Jg zlC+2l5n5wimwpMxpMh_9vz+sq>NAZvkfhe$SPk`w68i)RRQt84;@tPx^msqiJs1gFp-f&3qY-jnP^ zd=)#*#$hOQ4qU4|4`UF>%ZcE%bONsg3^V2g6=8_pKVWt%2+!Ri*GDy#slm-gzeCPB zzmb2oouInVE;&gqjL>ILBsj8wfbp!rGR}fBjFRyXnjy54zbweG4S6bmAOR)o!-&T~CaNqr=oJDP%66pO!y(CA-v3h^X z!t3&5V^5OHKjWNc5?j?y?NOosG~n@zPS2ffS3@K0u8!Zx*WHBUPw=3>$#GZ6vnzTa zy}$nG8At-H40MOrJ!0s+Ybu-~KYh&>-)cX7tt*}{4QE{@i_Bya)}j)(R-fc)vD5R5 zJYZ$EPB}f!r^=IQAs_-s#zqa1NL`1}P}jMeptR&rfEtnxt3enHba3vY!0@HwB;DYS zVP#1kPGT07m+1hjo(qu%$NE#4?@-UTuLni(ju#tPH9Q&^A0D(Zb2XNZ6~ob3HBLcv z{~$yP0xyPbBcfqLL+%jF^w|U`aP;1}U@u`1nGF)8D;v|-I@WI4 zxDCuhY%}kBB{W*$Lj#}tct6SdkqvEme0#fwiQMh*B6Vg*HqmZZv54%pkB%~=g;@f> zwzD^Y@FR{$P~hBcNDOutSzf$;c!W(cnFrzB7$fw2Oax{fr{(@e-oCd?LshXz{m4J> zIgnQadQ2YdIFPG(q{j?X8A_dmyYkB;S*-zI{ej^J~qI~Ep8{i`Mg@s(uxx;#vNrOX4EC(=ZktP^9s>XsL9VP%Y z;=q_19p{PXl{QO2iTKkX!)xdl2Me`nT+SVv1^ZBYZSzDMJ8`;Ay~NPtowv8MJehyL zTa}An+gJZ=I4L$R5x2RnemtkQP&@M?0fTH$*3csEJh|Lsk9%UqO#tWMR5m6)_3|ig#jcUN^cZ+A2V(IOL=`3FBUjEsud)EHpdFG^CYqf|Lzd6e zJISbnyirH_BH7G=$zQ<`n6{&yu|bR}y)AB5W1)Q!865VcOp;UX#g}CRc_s#NZf{5! zygxNEO)o+l?ReMJgetY#W)fUtmP1k6zBcVLZYR`jQ1d`0u`R=Vfl)evW9|V5nbdM$ zuJ~{>bfzIU-Xz(RCritt_G%-ev+!*Yu#tk62*NT~_=w7W9?TM$fry}}7P=z_q;M6! z(&~;1dVlqrt?y`K0Mqz4VkSC_;Op61-nssT!V+5Zk-gqNB%Jyq93c*GsI1MZ% z=<2$burrB-aVA?-jWjIP{T8-DBsh_&rmLyY!Ba-%Ik&}w5+p@B47LVrxtG&rmgLEg z4Tc7+fvF>Y6wHu^5prK+(Rd8_UU{zN$@z3opnZjh88i1n4XpfwY7+4Z$)x z0F4JjL!jlJWAD;qgSwW$J!B*qwIkl`@=5&L*z zo*p202O}4`B>-XcD>Q)ihRXX!DT-g>|2ho_psf7BW+j0Z&vAm2t=>05WC z@0JYbSovl7QJi!My0T=}VBY;RV~xY;1bU5f2C|fRC<~=*L(mI635wiPE+r~-qM1<7 z`L3u20K$kJK);Qw0iqfjB$vj65+oUBm)1@uh@67Y1|#|fhez++$#Tjf`CN1LRFV+o z2kq)C!)^vFV5B!8A%X92#sslvB{9ezNwg32@|@cMj<&(a_l5X90L?D>ec|;N+DlKr z&~96sZQnY1v^~3Nu|0C^M7vEG+6?m6j+51vad52~%{&i*u}YUuz{`#2NzB5NY=}=1 zy&?Db8Fpj?5Vfdj_^jbMWyOL1MO}f4FCkN28_RYu`OUdSgdGCVB}rVx59}h&hX9bj z`V*$*Ebd{s_?zXt3LiDZX`%*Xd^NNq2Wi$voNLlI3kLL~FF{L48X;^Tn`nl2E~*Q8 zO96dK+aS46cc z>@+%2ycZ2ZK~zRYc|Ay)IN=)Qbt-8MaT*BMaGI=wN>$hHu-qA1HPlC6IoST_9KAQ@ zANm;XI=z0fJ+?%Htfqw*1NGd{SO20%duqU06+gHWsJYS~Ct_6-cn1i;a$}?8aKbP@ zmg$wv(AZq$-5H}eq2Y1?nU8>LdSa-ZyT}ASJr5hEarT#Y8FAppu;pLb>gm8_VM0wb zayj%BK6NQoynBJ?9CGe{c%O8}I5@?nscK7s(B*oHPhW1PVR`Si_*&}B^?!)ZrO zpI}((V%x}%WrE*29%QpWK4PQFZ&%Shd*E1`Iq+m#9=+Ih(G9$j`I5(PzQ6s#g(Gbv zdN%Kncj@q%Ao-KKY* zbA8&L0lJ{PqjR2Fx9|c(e@a70*`W6uj$$Vijqdr%KWqwrsRpX#An&oK=(}KtG-MWg zAimgG#}+6fFS#C@2r4e1SKwGru?f!w5hgz2r=!1;C3(_@rAda0GB3m)rI+)JLL_$Z zla@2dj5JxX{&;f54=DOCj|z;fJ^AF5?YDpXx7(wSK6<&(pZ(dNwQqdm8|~qTA8tSV z;SY2DOJDj@%KYIU{$YFOnP+nC^Pm5G`~LU8-#+`<&$gFeei=b_%D4;(fneyTXa1!9 zdv=fA#aQkn3>*9%6I@*zZac;&X+Tzao&fc8uO4(#X-b6*0_ECxXdW>*oIB50a4kJe zoToI%mxf5a1Y~M!P=rfsa+AOFazX% zdPjJ444Gh-VFf~~R0EO{o>|`sck0>50mCkNjM+eCIfGQPilR?!>lo_M`Lkvpx%fI( z6O&VX3aW)tIU+uB=2rMG0!fzi#!!QxYoYQ(A?2u&$Y1^9&Ym|UuuuB zmCO=#r=V}!hSBzCFCS{p9eJH6<44$f!q@rgd=;b;WaOOynK=z)oTZYYHwbky*FX*A zsfS)mJ<0(ADaSYsrI3YlLdw=pButrdI(aqt&=+7b+h}WT8xUn|55Wsw>p82#3<*#= zBM$6R+BEFZl|lih4ZA!@12lRro}N7!D(wx#PAF3^@6v{(j&kvRwkKhTZ|3|GJ;ja0 z8p7?$L_57a)Q+$4tO9c*lMGWHJ-V-rZQb1d-n;H<2l}Sk`8#iESAG4UI8=-~)J=34hR}@43LBf|GneL2MVr4ev<9$ zW4X>343!+z3uP^1gH1s7D4oKzw-OYs>(7!$=K8QldtOB=*U_mSZk^PFKLpMx^m5%{ zPtP+nDL6h(Ivb2(F1nEx`L(grpJV&lSQ&Q#j*SVLxL_kLO*(~g0(aUGOA@fvb}kyz zxE6XSUa~9rf?j4ZLp}3@3`4i|15-gr+{+_b-}+Yj z&ENdZ_Nh;ODh-L(Y)F3NH-4l2`mg_bd*qQv(qnnyg%|Ss6TkY2_LZ-ECH12)&TJt) z)X%^DyZ;N>>uTG2dX9G|i&tUr3ix@pHL`E4E6+@v+42OVX7g?E`{g+|0;tAtXQDRV;2DMu00|Xqc zFjFgmS4jLLLx=qE+Zuo_>SUoI8lbRWYH2V!h#9){mG*VI1oJHU#SJhP-&$rP zs^KrOf#jvd*|z(c&$o*+Z?s2uZ)+bu^o{nOi~qVE*#9z)K`)NsuK|Oj|L}~;Tn!lD z)94}_Xh?80h&VYdy^Trub#2EOG$1#&h-_;I9^++BlkIuVzrDmutk{E#=MdX4le^#8 zx4%vG&9sl+w6`rV4|36QGI5}RGIVrZY{8;Tt0VZ=Tamp_io1XSlpF>T>+`M7{H5^_ z*v!HVT6wQ|6kxDOQSJec`54hewn;Fo4t(x8JZTzXeKDAw1CsmNm-=FlQJgw?K^3J`xpHtAoY6cMi%ii=JC~uXmF1B(Kfk|C z%zdqmo&FA+f*x*9oSJX@UVgIOc;ox&#qDa(BI{op+tU7rzx9FksfqRNFiMn?hzk9| zNzP-V@dqeUpB)eKp1@3nEQ?PsS4M42<-0QI?9~L^BtYEG&eX~jz6?~_P5)Q{`YD}iD_rLj9~B(P+$p(yKB%eZBsO*6sh0oh)!MiSLA2-{z7!z9G4wCdy`(tdq{vMB>-`PDA#@CWBM?CO%A zQew==_DU9&e&n7cLK(@+TX6I!`fEWiEG!_9LOwGy(>86&<{+6A9ALG_kt0Xi-o1NM ze~6VK!^6WF%9@^@&b5tG8xyE2>tT3`?KK|$qd#h2c=!wL+uwb({mUb-w0V4Q*$xvzu`T-DJvxyQJA_t9XJ7Oew8d8nyUu_wG3;#^X6Mx z-`4xu7yEv>ZP_y2-Zyiy9l7yuwZW}_zir<#nV~PY5C<~Ypglp3ZwZo*(9k_lMkA#Z zbf-LE1wG4T)5HB!$i1Hj5znkGIeN1Xyvourbgy+?BxX)5~ndTc+`ZvO2LK%!Tt0eg~busV>m^EPD)vpBq$#j z735`5l(j)uZ58+o5Ng%3(2}gy0N26G8Bmb(`1;W{#%j7J4xMV>VqdI*#mC!o`wzDh z*vGMpysCEXB*_)e?GP~bpFh!`t%qPgc~~s7B;HAs-wuo1jL|buZ~Kc zI0LWOusNqwE3448whw&&0W|W9>;sR+ED3^`{J5~g;V^xxI7#BB-SDYk4b~u4&iX#^ zg_U4Sc8-o>*YT|muML|$E3p74jRN@6_;5{5Yasyi7@xRO^C(qcIj-6GrL1;I4Sc`% zT$x5?f@W|xM#WBu3&KnTU_I!|8$RVL<+WL_D{C58S6hO>Bt*-uUNdkNwKCCfa$t4p z2_1%K!R3A@l&;j3`qGmAKMxxO8wL-*FS2v1M{P96_80_zdm$b+D9cQ;daYN^EWaK! zDh7o2zylAo_4F?0FD|!@JX1TdVUiaxU2HoM)Ch)ZHgyq2ZyKKBsq|Od{+T`PhAr#b z;dz1z#yY#W(5~Wr8_O;ejGQZDFym9S29s>S7^*Z1jg61S=rBr1!RQpSK|Wa<&cY3a zZrNl~I-rX0lv6`Z1~llSdz$s`!s<4JbJdN`nDJ$CV^spB$+MO4KfCSypvkm^>;0k6Em2|P`YVtv11Ijb0`9fZNLIOm{I0nPLn$K zZ@&wp*w%I(-`7qPP`-I-M|%L{o#1^W%jo@$3$wssG(XPS1s2-}tln1;i(7dJH!Drz znlfKv(0R;2r3-~vlHbBf^T)Te@eloGQ2`}{DdDbUP4;C&1H1$kbeU`#9a}FDq1R4L zbV4~ZQg=o+qZYop7N0vGOV6OhM#t(>dRS%*zCK^7+Xdrn6R+^Z&G#yEdsz*reF#?@ z^F9fHU(3jP$JT4w#gnJniSuu?f4lCU_L0%&+kYP4+wP_T*}L$1yXM~awmbGrxA{eE z3A>-ASGR~Bhn$E~KFTF$>aBEHa&z1Bvf0Qz!4f;7nj+P@W3!!(v@@+t+f9SyeGBv3 z3N{w*8D%n7_Flbp3UNVN_!c&aLVEa+mV1KebYpU6-6TRiS>lU*1M8_*I(8+YxY8q~ zw7wc@8QRX!nefzShF6I_`gUxCU@onpg)i+|AGV<*E~NK`G5D-ZRO_D@P1$*ZF7%~A zi5#&M^d)FG%b&yybxRUatM72&0Mj-o3+_lFy(Q(QK#T=mmClMmy~ZQ7TEzRVFuJuYkqC~6MhmC%3G$9qQ@E@i3a}SBOF*tPSP&;_= zV9vK~+Xh3I+P;1J+7nMa(Y9{g+II6~ti2G+JpTCOxwdoX&UX0l;dboUvG&+wkF{HF zxg|I>C=GOcd_0ZKrpeJZF*3n!q^&)OAuZU|Qulmx{^ixtcGvXw_SBI>ylbPM7e=wl z2%&E0o#TrPd(BgRfKRff1X|#u<-8UXLsaA1bqtTX)yv2Qt{b&=HVsSRQ$9Y(b&5nG zW@8$lK>0R+c-`?ygSJLl2B}iXYfSMl5Gp`Fry5Xz(VcZ2YwmFkJQbLz6_`~P5F8r3 z1Skl8rgtMu{!5>?_~R|{<>f$TMbNmwvlji({1EdE1FX7QLAD>DG5_GkskY?sO^mmK zqNbO*4rr8C4KC!EXa>J+D(%Ia@z)yks^BR)!w43XF0#Vy>pkW{#9K z6gY^XCS0!VFi?hCXk0D_VDW}vs8{wbN0najWpx!}eFP>t=+Ty0*&*fcGKo9ZE^NeJHYFT9a>tLIono9q~#<60gh!VfNNV?OWB+Wl}B_Hxj8>%nASIX_6{9NNxxBk z(xA9F<6`KPqR`^ELr>U?J4pD($P^Q{C-M@U;I$I=Y9i>7qzW0jbX=Voe4__##InlT zebQokSV9^LlLJAZ->_n1LE#=Mt99IpIxIqbmV^Ve5=&v(qhE>GkM}r&0J+B3wG%EF{JbG;sRzCOhydl&@~Ig7&aq^^$6B({fljOePlg=> zi~?`L(f_}4{4Cr!I>g&ZxROdmNr;VwjltKx_O;B5JoL~*>6P4e+imTuU;S#zJ^0{* zX&fx)g!?GZ4}9!nA4`u#Ji_WVB+u~b*%!~uwCN3-+Ec7RIgN03#h6tM9WxALVsO0e zo?732NapoC6N+2r4z*Y2*#wZlcrD(y9$_j@?Iq)&!O_V+g2u2Zpf1w#Gh}7qatN__ zQwPx~Fu-ghL3V_V;%2Fsd#149GeFgFmWCsgF0<)yZO>5qmUU znIZ86P$IO9^y##G2@@JBbA(2(#ULS@BM$0`LBp!x>xaK|jX5_HTY;8-@AF&#sbB-2 z!0oNnP$veFtRw>vXeb9A5K&{!%ArDpfg>xx2#R}Id9{hR`3>#)W$ur(s|h30ObB)EA(VO|IKfZp5Dfk|8OCW!aJ_g^{fdlgZX4-xR0(dkD1i2pQP|JNKxm z&1RS=$dn)QRDT@WP9+!BI0BKl1a6haz4AruJ#&2aSRg^rX*k4Y4gW&7jQdNwhc07- zV7TSgsssO$;eGwCeAXiRs0F19X!?dYC@(Dm6Ph^#K;|RlF~EUkt#U@rgrMAP{NWv# zMH=NJUuCTxY!oeNZI7Su&=DD2y^TBL@94+`=fLax+dJ>Nt4&Nyq^5AL)ImH^<^Sd3 z<)20L^RC$#{A4n_;QYLDKYguN=L=u{a{K+c8Me9D)K+-!$3Ndm&jO=!_@h70#+(Qs zeaFGT%2<24?_~S*k;V4lwk_@NvMzO)2isO-96F^!DJ7RNXh8n%v8QA655>o=bt;u+ z5Qs6Gd1~NQM_0;TR2WC?!=$3ikSog-pEAM;x#KFBIB5(+U1|Unk%p9@Jc<)vdOR^e zow|Zq$WH|`U$2?rXxtqb%A5BJD*;i1lo9GnLkyaruF=K7GtO-fz(U>^kcKEd(3;+t zzu}+2TXm!_cN6g9G;$!_8h7Ls6oTR9Q9wp!7nqOo8JV5*Eaw23p+WSh425`^-d-TE z5$DL<4?#NeRkZOO@EZ&wpJKk`4&c}XY}4Wn_#6y0A;Himo572trBN}`h@&TSaI2H_ zDyWljerZG|X++R_HI_wl4VfVS&{VK=VsU1fCVcroUTnXUZ8FJNY>4S}iw1lpsuOlD ziU3~Wfdr+4?1$iMJxh20{SW>_``pm2?T*uLv|rh^(4N}x&URvWV|&l})9sh9-_@Qw ze6CGQjkhhsll1b;oW-M@v2_ecQ%~|>ls2p9FfeJCmg!@@LD{S&d$v*Zx21~Sn)?8V zItw4(6c+cRpE~U|CKhv~z)5k343hv^P~Q@Y@=YVGUH-qsoq5b>S9#yhyRS3vcvg>% zJu}`7cnpRZj0tP(;G`ym5JHj)mnuz_C{={2s%@l15r5E1{i{W(3Q<~6QV67m1WHR; zY>Y94#XyYh!Q0p#kH_}xGw;4mKcDY&e}lsoq|9jU@BO{Md(S=RInREcv)pr~w&ZTF ztFF}!?a#bgdZ(w}_M;C<^_@=pm0il@OMr(SI8OIP8odZi+8qn zz4Nx_y!7+^u(D=Kkdhr{lSWc7;F5(^pWZ!<9Mgz z>I$xuM@f+0DR(iMdn#YXYe_-SHJPTcGz?a4>1bWr3LM82GkFD0I_(-}DZ!-y(5YLE zQjbAY)IetHEk#iSU1g?@Cu-4w>zJs~VvfPDFt(~|G_olJD1OL4afz{IlQmN;qfE?0 zey(c?V`q@^g%6<2Nj|ljKl0j$$U&b9x6!Tn74mSF$c$f8>+OWDh_XEMR7Apvpq*fd za}nLxz!UGA*|~QXc!JN+pp2k&hVl3T4Cl%N2OWba0UlOY+c^duo|z~PZZabdhu=`8 zwQ!~!`Wd2=CYi`(_~8(PrPT-boeG1)*SLewp|kZJB+{(>)zdgbw&mBMz$)$-FpwjB zE5yyMwK|U_{@#lm9lnr8=*T2LORPQ8@RKgy{j35TAPUUtn82kx3qH(cZGFt2&4bhU zz`FKxSH7yf;n=s@uk9Fa6YH*Nw~mjschg|Ja>qsFo@~e2*?5vAze~su@?yv8QRGOz^dKnX>_;i2N@;p93J(>1EjaK_@hL0FxYF0ew@{I` zqn>10I({UtjSfCKQ1!}HV#pdaobz+u#S6KluM)aP^~j~$G$kzs<;YDP$}0q97GLO2 zDQyLMbB)4>Uh>MGG)$amlNQOM5ok|+@YtXub#OCwpnnxzj=a;yMIL0gEE(`g)YI8R zdS@E44GWnJ-W+1iLK3rU4B};3uLZ<~l%1`kn$RBuJq8xyzn|eBllFZmb+*99egARm z_IAVAa{J$d=jT1*n;Aknwm8IACa4b#nTJW!EOql4hR1fWtoXn^?L)^lwvnB?+Up!{ zB9AX|O4khq%92(UC_@)=5=Dmw-B39urL2Nr6!1ERfk14n-A>tP0+YbdaIh3l_t}UU zDb%nDAaU|KBb%yDH3+_F2&g{^=I^5QC|Md7g$qpTP^21@!gpt0{gBZ;2naUysAOKK z&b7)yts-BXqVTPYGEon^-xMT+`1n5$u|c=z-pMv6jKk~Js;7m4sipk&`v9b6xQJ6Q z(Ia(W67&FdfD-^8XIt^-*!=GVJI8LGT$hz>OTF>^{&~2~DlZKU9%NM+G>Cx^<#h~$ zdlk8p&JA&PNo{wNE17MBB8?ghcqLe<>rg6gaYW<>Kk4tMULV{`G>nXtX*qlyk9fdq zm@WUa>p#*;z2M-0$F}0vk{*(sauTMYqZ=GZ=YUv8+b8^tek5T zHgMG+a8b!-mztu*O6a`nTq@<3kW;L!C%+bVQdkI}JFy7|mamEMY)SuK@uEuX~QTk_3hGio3H zihKT-^AwJFg0CV7j?o!wAWgng6s}Vb<-x~B*WDDdN)m`cB(&Q2hzMV8HhuyAP)x7T>=s*3l#d{s?-1_(>517kamPp6_xdhv@7TGsozH5pqbQUuU=1Dh z6c1ywm5-x#{~3gZB4nrs1IYwC!f+Byg{&xFB~xj1S~8n-f3h+~#kBFU)#r}divV>V zW>RVEMk$k*bpU|-Dr@!5C>xk0ixbMIf@+XD2TeG_PER7L23VJhnreMzcsiqAk0p4j z92g})gOmBJD0KuDj+`vQu}hqHT=b-_a#G5PSaamv)DqtNa!Tif%l@{7$ae@_Z{PD! zJ9Od@^HP&2sV|HYFZo7BB5UDmxb|8!MvcOzHano|jKDW66h~Q*23y>eqhI6_$E1;Z zABuEZl&vM3<|S1)nhlxEE-&IKQi6NnoJLuz>-bah6kqXUqsfGR@>ULGY~};&8p?71 zH1k3S_1l0}G)Wfpq~bd0g1Rj229Gqz@KkhyQgF0!5iEL;-Z8bW%?EEMw$1G@lY^EE zTpgVsX$Pj~*-2oIr}*uSx>+W)@jY+-L_q1gYlw|xG5o#~av$8lPbXu=kv+Wg-61+6 zaZJNXLz-o!*362eG=jiNUH*_aK~?Zm3J8maL5?UGQ;hQcR-+=0%Bg;peu6Xj5vZnX zch(0k;uT$$H{NqDLkRFGcLXI}*ox8-FKH961R~}Vp27#O=zvP3b2oV7G(Ldxr`A~A zMS~HW17ubvi4^lr+I1R>P?So^3(i3r9U|#;Al%mvoTcL@_CU^3ou|Q$5uELvf3zJ~zPepL!4~5@g|;|E_nqa!r&#*9h-39_ zYg<_RPFJK2{LIVSNmhGZuzEqe=mk8bJ_qZNa83BhDwUZfovdalebqy0H2xIxGX$kl zFl=S?;5`MD2(638dSz?uSm$clVpSZWs*a+oXxVUH{ zF&c0{7lEgvl|J&C55VCGr#g@gIM4IWn`jRnKHT;^_f&h`_6=S-W7aVnrM4WICA)kDS7%6G5OQ+D)C$wsi z@j;RR9kR>@#K=>q@^**`e~nhYq%2N0Y!Ob<%qwgf7d{yx32@cIB0BTUjVheQ#`}2@LqnBIUsG8f-iX5L$s&t zgMVqfD4%dq{q{cmcc{^G3uhrpS`pL2Y9E9 zb#t2$6C28suTnr`2hisP%1|I|3e@ntW$|tLH0Qk1cnHT#Xa0b?b9bz+4z-`Z^)4Z47lMocI;*qp=b&?-{aLhBMSk-hg& z+v%PZJSbea89oU}QpHhypkC<(uk=ikpJ<3X;$u&vqIc>^dD5v@ndmIsDOX1+uT3in zL!&L8^009g$MBYZsP+K#En~i=(|Xl$WloJS)1WAho4Vwd{HWb+dGL-XTb(`Sa$gCi zZdX*MPVzgX77X}R=L@JmI?Nt!-~wFhi;f5;j+I0YosSbTg9um3bF_0~tS}!H&UYw5V88(R7MPA;sK7$W_=;3*8 z9YrJjz>4hlTeq-0bW|3LlMq*V3_slwx*UhO%`YX$2H>@lN|WKst;iI)MJEVpjJVYU z-xBCijhQF%m6|&|O%LhNCT#DWc2)w0XFBloT-^!>-FD%?#>veLccKSw&q5l2^QqyP z(%m(68pl^b5^}K)Nv1+4lOuBQOJkz0Qyu@wfIcBp8z0l8$$5_GO>w(RhnM1S5z;y3 z=*pWy;i9{1P{AcM3e$RVewK^eDOL}VdQ-W1U-Qy9Yn z&S7+L921{yvr7zNG3PPF_6% zXpA&!@zA*%gj(>BnljP9hB8*PWvdA%+#SQE zdhONKEcU36P_o7z#ZPdg2>9VS?FVBb>dx+vrwWX8A71C?`KbK9nbt zmuE)oJ}fEz<^iklPwXsZ8TbQ_dOe%iN}wor8vt>kEcvUa13mJHBdO{$g|ZMpzNtIW zJE)_fO;B!K;zxaA=%I-|!pO#x*xm3$``ez`eeKch7q@qfY--!ti1Gw`{kiv4)sBy_ zvM8}MfLG|}Tt6WuB{;G$^i2IbFWA!7SoxRLd%@JkUAux~;1b%vN0{SL&M2B`enM~Jzjb)M(7}SekV#fGjpH9N3i&dFK zTTufXl6H)O$yB#WuEs+pEg`CHnShH*sHLO}1C;6^I<6?}`?7tFsk`4Q7EM8e7$TA| z&{UUHh)nRRF!|_Ae9{%8GlC?|`)(Fj5hS^QEp9eCIwKvAA7N+TEC72uPCp7#W34tG zMuzDP;VjS?%4n2CLv96uA*ain31aiP5vE8=PZ@Fzfw4qc_UlL~>wD9E0Bf0r4RP?` zG&nHTo}2A!&$1ty<;`OwT|-P72#}#G#x4zM2&L9^jiWOl%47q_D6-Cmdij^7qco@* zyA5Cp0;d`eWdc8x2gX%BDCI*rbil~b``+`2yv=Lm3LsG72fUG6aAp`t#}&tKc?_(2 z3eqFI;!&1FYPixx*}wwdtkI9|00C6PPxOz{;X{UPBtQcW(iX+3@DaJ>b36gK&hy9{ z+Zplct=?0T>dT<>HNxQQ@R|*GA97sBvmzBF!Nb%8krHmbKvEu#(d&rS-4);WjeK1i z?@juZoaB^bcD$y?Buetot!46_5S*nl6{J0;42_0vdu&Qgxx$ZB3td)?QmQu#^GCt8 zBL1vS6E2_xwgUcXAL_sQzbGL!XoDjdcl>iI8?FDq_DNlfv zS3O53XgtA9HQ2}t%yzQd(x(qR-5!`^3z%J(w7Wiedwb-mZ?^#+%+$drGt^0rM;`pu zHbAXR$;O`x60|SzAOZRr!s3wvZ7Gxa>Vy1)4%6x_hhU++I$e-y%Ga;kJRvm7T6g`X zvCS3NG8jPO;QLjqQ#|5d&~@AY-Y|I@CtztS>a@Jikg~Q?2vh#d9ue=E1^LrMLp35O zTWTGwVjS$7V8_u@?E$t2fBa}4dtteLU1ueGODM+G>1xB(8V4-}ixOx+a!x+S!74Zn z$|M^Sl|araEDgY>N5ODzuY-?9Z+>#*I+2HENYGIT+hM0fcbuynDH-FHD$~eplt9kh z2Y;hV_iS#-r1l%c6pQ=$M6;D)wZ>Z%OXXW@ey`CTt98)>CI&ocl<;1WxLZM1# zULHi*;3#5p*$9T{B=_Um@}j51^fFZ5JYNI;%M9D;9K3coCA`9^X7!GdCVA3$YgqCH zp8C_HO%@>8pj#7s+=LJECd+6w1U@=Z7)x3CVx%jKGET^d5vP2F0;h38)lhWKPMGQk zGExpT7XQJm*A7d8}g^=AIvENw2~4 z453m!tOlR-QlJ%j#NR&j+TtAfe0EAqO71xWo&i_pnzTEpJ<73kUO9lZXvhfuY6z1u zIp_XaI>hUo9X$u6@s*R-ZSoBE(m=a@`(>e9p#>h67isPhDnhdL z^)kab_F)|c;b$2`TUpUj%4AnURd+M%t-Hulj;j!9-6@Gp#9;D3A=e79qWv-`uT`!n zPB(<)u(pEK(D=uNh7A?b8QAMd*HdulMODaTIYuO{8LzY$^{E8HvB&In6vlL}G@K|$ z&#*dWtTgyn5Xh%;0Vk3bieK>-9#vrKp37-(iN7EnntAvTFDGK6R?NKVYd9ktaMDLr z6YnkYp++mguzhf?oGxYLL+Vtifdqf+o;cVjnAcyfX$;-6K1DPL>t(3HU-M>&1sME= zCc)~gvxXmR>L7Oqv*&Q)Tu6%}rm&4dmR%Aks3)a9oP&Sw83uccg*rGyFQidEA|qBc ztr&HJSNJU1$bT@6P0@mtgAVZMQPoh9d?2IvO8}K--Xe1Am>wM2kPrQg!02Tg#7MW4 zs%zHou;D|zd2o_N4QoWK%6yHvEp;Mt0Ct8n#LsHQn{PHewSjSb{&czqWPJu1(!<_n zJcFX1TiD$fX1#6WqqFCl|NJoV4~<1XJaUKE*c_Ia*GLa)D!jD0HkQO&f*&UkBsfH<3Cuv zxo-5?_QqRY);_asYrE;OyW1-JxP5bFd)s%2#$yHq>ze#&`uSJ`oGW9K3Sc$0?* zK|Fv7kkOl zqn~nQq@Ll@QI&4l=onx`N-3jMt1e-Lt~&AJj2~+KIXg;NC|npOCm59TL}ZPAH_l!X^=*;hl}EQRmPQlfW4GAm8OIv$aqFMGvdNlV-s|8*))` z%dwcIW06V6dw2f;06+jqL_t&{RXoa5(WWxS93KV#*}|Q?I@;>}NiyYB2%(6#4l=}O zY?SXgSsDQy3K$|TVCjHxupmRt8lOEFF%cKx$%Bp#ox7>R1%8GPE$djib5k*PVe6pE zkfqS!9V76-2ELH<6gR)o8KUm=gej9ADtt|kQAaHf@X%e8(;J|)Z~y=<)&;&XaGedw zlu*wjJNZLi=e{P#+^Kqmw>{QyVh~+Gkddwqf?u)5bisafiCo>k#4SBR|o#sv`)A zcmAZcBhWxgS;6slO-PW|9TL}+ATB+%E-61z^WvG*9R2CkB*%t{D6ag>oI|Kf;* zCmm`Hu+nV}0F}XX1kAk^p4c05_(B*e7fOTQ;-JzP6VQmlY8XZ1vO>#SHwq~1;uxta zjj%LWzTDTi@*=J%x>2LVKrcFGmw9D%q+K+|%ZcXZ+7*)%ZF+(HEO6*!lf*a|ak7C+ zL#Po@Uebgj58!BcXFB|pR%Iq;nbyUqgKM6O<`lYXs0PQ61JOj|d_q4kbShdRQmpw8 zVQL^W4QaHY3&XWXxyDL4UpE|QU^eLK4L9jf%fV=L<|44!9_yAH$S2h!m5FD_h9wU##lSZaEJ9r2dlie zinnNVSa7x2wr|>sKHz|pEes`xb0+GjvL?U0qr4>zx;*N=D(aI%W7>hXC8}avdZ8M4 zFV0ii1!iQSvIDcWWhafKe&nHUrUzgIPGQT%X{rN(D-OUG*Zflkex%iVtsRT*}ds^Zpr3osOwIZPmD_yo>O7|WAd7tejs12@Z36X!Y%@$eRD{HDAyTN8j`lXp3_amH zeLEWuag!%j1W)bBXa{2L(eKuC~y73P!5#!nIY?$VNTES2l=!N zKK5?34JY{P85}8)cFL!L0-mrb8@xN1(Mjr0UG^3o;4B7jiKj+k_mwz=hR4m>j5?z$-0`UG z%PGUhWxwT=jVf%!Z23v!UJSzsk!Z@XTulVjIfzaWFbk8VG;n;FD@NGqgb}n(OzxZWDf`GIK=I|M{+F-^2Qa|;@Sf~@Cb)a&}Z=F%tz#tA3T5!x%M%@MqOGVli|SW0`8S(`x022FV<#mBvH_ z7`Bmo)}Nzria4z}j3Hv{M<_UCeG8ITPNDi~ic1rH>h%;3}O zhgc)5l2+@l@}ejjE_%>YFrNg*UxJX)If|e{M@dSdttgY#2s;rk3d$V|1XmlFQc4xm zbiW#f$_pfk1z#iHEQ8gtf`ief3X=zaVhj~+0Tejm5p@nMokd^+Ut_ceH7DbtDiXah zetD}ul!rc5i)wK>OYEx4yIWrQ%693-skWf6=OII1gjDqkjCy*;p+Q*? zynL&o732+#F?z0@{uE`4_i=Bh&5A@434@HGNmxbL{uqyI%GBcIAeV_VjTA4tlSBSrt>ftOq`+A9(H! zW}Dcf)0$jKm&tc~kI`;uN*;NO<5CxG6!;iG4YQ>8iJ7G~wT?{o&cYRbq>b|GXZ3KE zD>UMeil?Sm?;~$Rl-KIAt&lv*H;m*GSl-hKQ!KK;6G>5ae+DldmxX*^PWFvMcQS8) z{rI|7d#3hMGGC!wQjKzlKMv#hfxkk}3cAyFVP0UXH{5d`LVEK0;V#NgAR)jG2X#li zkQct<*5&8v`3W<83Fc(S9>Cc-p#jR5ju70@$l2TRc_SOjER&`zrJ)H*h36Gz(;(*E z*}@??I;B5Q$G|;!QBQgRq+=86T!wmsVllf$DZoMd*UJZ(IDw`?Vj59!vMi~YONCCG zTbU`vAQl}Czm$6#FJy)vah}Y8G()DMX@1I<-FSX>j&s_8NS3CbPXq^}6hIV20jYQj zN!2NZUSo&SL&uM`7i`=@8W6~<@%J8-C3rH?|33##LfzuuQr5-P9hshR&aJ*o|~(^zT4$iE*Vgqr+cWg?w@lCo;p~ho%fNf(t7eBRGARgL?Lqlyh>#(0{_$fZ zOnMv`#qE{lVY4_Z`7SCNfPdbT7xG9tgyDZ~ii3@gDdv?wDI_7%#uz_54ea!k#1(kt z<3BhlH}K^*=m8_pJ3O6lTVwc0nl7H)02~7thOyRN6p6g|&b!*%UUzf5^1^@F=I2kg zPu%y#wr9(g?JZaA;t>3avKw6S~`h#d0{XBk8z8@6A9-~q#v5&WOUGE>1@MZ(;AIH zMrRm= z%mEy}+Ap-YnPncZ1#d}99kB~;`yre1)XuUgsJyoE5l3jrfAEwqS-iqM8@r>&j_}&- z33_qpDE2luWP<}1l0jMF3f~@cULlJ@hrBc}#lP5|cHQY=?x%qgCl7&v1H9434er!a z-?Erhm(KBkk1N9rT-1|Z8IS;HXJeF^(9|LM3CN*PrG(9p5KeE!SRbR~7zGEF9Ea5~1zww_Pap(=P!r=VI1 zf{)!%WNCB|hu+=SpsGQV@!Xf`2+9-lITVn{h*T;8qD~9AX+(5Dn!SP)H}BbSaBbaO z$H`Gt2by6MaHs}H<5qD@=Vp{t9EC%JMS-m360V9Q?lIt;a6w+uGnP+X;;W)!LE($| zDG~0ZyDbh`9DpkTZ$|o{l!qdTx|jlLFWfwpJ;)Xu;z8jx z>T0;)mBisdJhA|Qn#wsPC3Pm9Y?<4Vx8w=$brKqgbdn~dI3@AnULs(#^hk^Ws0pUY zZ|U%(aVZDxX>Z6I0Pxr4qn-;(e%afRUCOmeIzgHZ`ZQkBLXI-fX>+Y^&9kM1yB{pz z2#zi<&=Xr||LT`s)2_bor9|4>+0Ooo_Io$JvCSNRuzmdTr`yhr%)9X}6Sd0^z~bC= z?7B2%55xK57Idh{zh~?&nI$_pkR^?Ty3}tYN`t6oH5#8Kc03rwChU3ZVwyu9OajYA zdDx4*?TzFCMd>Xi1djFVOw348b%|f~T&Lzz-1OdbqFg#@oy_8uoTP(~QL*~uH$%NN zm>#m;cdr<_I0TzT3dmD_uJT>xo>=M(3nI0nXR#v*SHFPcsWzt*O~VG6%GFVb+D!qh z)NSPBa%FL}Hw$E7(_lrX#ZN%ylMZV{2Y@xjtFvvyV<$FJX*tLNofX6>dwU&~MnTW~ z7rrDAfgW|xV8g`dmDQylue)<-jlFg{6u3wA4Mp~3ahxMp{fvR5Kwl<5M^hg42?(Gph##FLhOu5g~J)k z=Qw8a%WyAC0K#JhmV$v>z35nF&*U6YNQyG-lKPRN%O{6uF?vfn0}1ppw~flw8* zmbr?h$kkp-I-fWQ6(;;pOya1bOQVek*cqXs_&RwbX6vu#FoHSA+N&@f+)y}aFv7E{ z0m`hdjkWzVENz~7yd5~czdgk}KDJF3l>S!D_B zY&*JmqCItDu)XDl*SFE>C)?-tA7yL%aq0?T!Nn)O#WOTey|~Az>bN4x(sePBzDyq5 zg9;8dhSI#&Mu)~|w3hSm>1q9kK6kjSr|6X{?8rxG&=17OTlVUF2oCaIaCA-uuHg^q z^%`D5qdj8*RWDCy+8w{-_bTv$IC=)2nRlr+7(A+chMb^9JQH-$2(K+1jsq9YDdwvZ zJx7P+hZ2`(-8afax)gBU$FpDc5Ug?~E$9>UFp*z@2RO@PhOslmYD0o;>*VXX4(}JP z4pXNg(l>x%XyUedWuVwYANun z^VOCzQ6*966>W6FHZlQ#YLsXkI10$m4iSPv5*rddbXI(fXsth! z%${-|i0B*%^G!t&{)pH`_gMB8x!gAoL>;KfehJ! zN29|^A?I|QJ6O@^McUl90yHvGpwX|gF{K}!t~_{%ElR#Ie5@Thw5MIN@a6V*EBo7B zye4>wCyMPF8?~iGX8_VHg(N0jx}`QUQXcSAso2P5yxB&^o|aChuqeF2KOVt*M7V3| z5dkyxh_7nTS4Bw4N{d{m(fY(;FhgBB7^%T=DKl#@3J2DpORodL`rscHOA|sK^~f&} zt2*-XYQWXEattspW`sUOoo#rD?d&IbQ{WYC|1*!ZZyu!)8D}oQ2r|Wh z%(?vYWl;t&HGE|dL(K#t5)Q!QTEIbG z8Wjt~`KYuOj&92-9p4D0InVw{%vVrf8UgV0b+lF}Y%z*6lUGR=6Y;Wc?}8mLs;5r1 z8lRCc{MUAZcNeG(v1#Gp_~zJFWL-Cbp{=7A9S>?aO_bGd@%Bfa@oO0a5pjU`E+}z6 z)5fakgBRgBn8hBQ2dF?mo=AHda`a5DML&e8o@g7?V;vRbAO~=&-YxgEYhYx!Fb%PF zsB5N!t4=?%Qht168t4{$@C}rlkcE0uhxsdamPdn^4NKvtoW)N7>aF;REihE&8oJ<` zBaJ=F>ZJ|d>qC^qDm`GaDf>~5(2X4Ap(4^>o~=Wvox4Nfg~<@9;=wW5=qqM3_UqWT z0`tqcs-!`(a~8{ZS%t+$ha)gmM>9&gicl>+Fenl?r1C37hX#OP^xwy%SR6FT4qGI8 zR4HXVrF7Rlqf|;pH7bG%s8mEpgZ8-tzstNE!-=^l2f|$8yUYu(yr&?|tFlIEbXF*t zfkliK#Wy-N8T6~09h|63W@tR%<+*!uH7B*f!6b??rmF+9%!GNypAm%Ue7YeQ|*$uXL$I^ z_o?tABamET2q&vndde+IMn@VpMqoc6LnfqZNAgGZ!7PfyO`W_lpuR-I{O=IrSBWZ5 z`CfQN`H`<~CdO|A8iTPEgvflJ+D(tjir{78Q*~IiIXkCnP=RCZ z@&>vC+q5_*VR}#OI3trLod!BGg_ol^&e6^7tyf&x{_=r)+cGPyU{e z6t-`DOr&pxz)kNob(jY{b9i3&tlL4A#}6NDW4r}2!-)I0b9o#7fI|WlRxlQ?)nCdYwCdGjM}aRQ$VHk#HGz)$D=&aKM5C-eh=K6b zD|zI+8|pv-Y?yrNP8=h=)fUCmAHVX(pu!$_h8_bOF4+ia1O_hZjAnyVPp{ckMg&9Y z&4FJvWZIY1P!7I$53r$6$r+sb>zVp$gBO3JoWdLMXZ2yo6K4)(krD(@Ad&L95&bm@ zzJaIIM3lhITL`H?)X}(9uOUQN{gDNA>z{z>aIyYP&KU^1_n!f#w33AXs9{QxtRXh} z!VOJ`3}thkC!zETdVE7+9pX)C_^O^Ox3hH=24@TUPXRaM&oD)27lq~DFiaiA@eHE9 zMtl_&=v-IY99`o%WcA$iiFWTZ``i8#^Sn>wSUWhw)8@!>ahWyUIPKnAc;B_1UWiJA z#Em@Xr;oEG0t!w&nS@HTSgVFySrVhua!|Muf`U-FY)ndow(M&dpsO@=lq)V5<_&TS zYzwixw7{3^MI0e>5IP?7m0|)&WdRQrC`zEgWGKq!i+ol>wrp`B4^dvr>Rhum2seEa zdVqSSFWS-i7v|cgyY6aRzVg*Jv42lHv|~rx>kFUMBLC%Ayir7yiRKgMVMBMSZQS>* zwr+f&{hQTI?Yd(Rw#B)9?TO=aZIT9h6{54FC#_qaQ_xs!==l}JINSV|vr&d(YE4$^ z>K8)-qfHw@RUbfn<)g+yE{4%m@(ddQH+rCBQW->DM?%R=iXtyfqwWa@u=!`BuOrkr z4MgB$$d+*PBTMQH?eN~$=PJLBEnSKJcuii3E<2eG%o>@E3}lq2wed~B+{ zo?VN-_3V?p;8#jFKJ|jyS-q41iF7b;%7xjph<60H4Y8X;?Q(9KqXDSs)ue@LhBTKlv#M8{&HVl*5VZAwrHr1^9IUA zUZCegqt-dZ?#s2R3mk0`(Td=p&dBS0x&{%Fr9BM|m8i1CUrzk9zVLwbvvA1Wb9QLt zgD^zHN=pegt~{A$g5${Sd|O~2tXbYaF@vExsp}T%BPiK8FP_>oI@zw>vaww}HO`8m z37%n?A=u=VxNOUiVWlVptY-)*%ktu!tVf2TxHK$|RcCix6-H$-k5O2JV4AEcg-#cp zh9>ePgSag7ynxH?7P0Onlywn{U#0=`sq!VBj$WcsS)@@}V!LRKD?7+)@H#V;3DKu6 z8>tMb(a30|W}`Q{pqf$cKh5c zuLs64sJK?4wp&NEUXxvJ6O^CnJHI`->lN*Mc=umtzuWG+==wH0w1tN+iNlaXz2cR~ z&mKT5Kv(NWj4*Ow*SK@_9=$e)3NMX1L22y0hzTGTBuN9(m~>Ra@mydU2t3vhWJ01j z^Gk%Qe=r(rP)4bVvcmVjjSjynjuDGU;&)ETYw#AM6O5k@esdKEJ~_--GP6^r{_~wfhepu?=d&;A}$^Cv072q>P4a^QuD=7nD+f{8i!XO{i|}BRL?ydZ)va`a5xvf zN9xKob%>xcP%hSA1vufN>m`6x%GErhx4<<;N*7CiEsHF;SBnO$4BX8qzY7AOz{K+M z(%~t}xRb`1Jwt|=bpdYSFU@IOg$m;7pPcDPYI`P;3MDx9F*hkD>NdfSh=%IL+8q!r3GHao9|+qn?KPO7I=Fi_ZOGu z(o4yl3-x6c49b-uw4R}6vQG@e!7={MVMKAjADU}TJ*Y?;G}$Y=@{T*&<7dap8dx* zT>`&{+njSUI4?QlS7y*8@r#C=(lyZPZe7Rya4OSq9J!%U>P&?p{s>%qvKPZ?hN#3V z&XXJ7=bMHFsNS+xot!I=s&~(I%WQ-D zOD3av5upa~DD6=mYKJiL(k;&Ak^DXQZ8vgcNIMAW4N*?*#rs?>}IePo{SWzBy!Pr6?VJp`PEI>lDVgw3h z84`7ir|?Dm(R=GdUU|-l>z@2zEcXR+<_|r}IXf14TJ71Hfp+Z}LsRU)>xO==l3G8q z4Iy9_(CyTw0>B->G{&6~mJ?vArhEMC=l?S_BtOr5zyiZIcQ7Qf6Nc?XICM_qys}b9 z1tPD8v2YZ!3~ zqqj$+GZkPVMK z+WKaWxBa)?+}^QzW&7mU{FKs|>8`dgw@5<)JPjcAs7N+iEK|iPZ{{Jmx1Abm zM~7bmp9b44+0uoVc!AHjh6R>7IF;F<4jaze-de{fp3q`c%el|hWYnIrIuVC&G8Qf^ zagbdDjPdxI)qeKaa=3-u;HD#iFdLFk&AIi)P{9HaHUstsYp#smW5%e(!7mLwcjXO; zaspiNv9U`74c=zxXHs9jmmY8}{tkKRKrO1pg8M1vnQS~)NOyT^7Gns&VMgjw7yIq$ zP}C>*_s=i2E2lQJrxxfju+uB=f#c0KnwB`gGk%2G@7o$}ynqEqlkWYJdyl7m?CI9u z_C9mCZCf|Ueq3YBO)(z@5Pc+|QZ~=F*g)!3)tNLB@<>{%-rjQU3~y1&VNz+z!+!G9 zIYxNn5LO%>O0T1jQ;Goyo5ejQ(yz`;thKZ&MD!l16wW9!S>#s zSwRTA#Sv$r20X8_>L|DiR-OSfw5UJGI8i?-6ND2DyFAh<1QX1)h-*D$w>cfY_cmD-Zc2pacNm%(*^pfW;Ex zR;%eK7kbo@U@MYm{E(_2dq|+)>be#ZKPgQ>wIr}W+D(3b*&nRPxBXqf6TS27ysMEK zOR0KujLjQu_-ugVzK$=-^gOXCPJE1dFom|0tVags+XoKKqCCUxnoX@;%=X*MqAQbu zo2OenS7-=9MR80xPDtS@_&_Zs2z>J932U@b+;m{}Y#f%YXda~khpd{?*p^xGgELhH zRc5!i7Q;mvH+KQ<%RXB)J_z4U3VoQ#$Vi{%IIK>jzcG1T5TYQGpy4Hog$J%ep>l9h zQTY<3&R%>$J3(V{A9TIv*+<%;E9cvhm%O7r!dnDyc8McHTxb{6SQp4^Fv!IydszpB z4Q=App3;;#mpEhOAx9L0GKi7sw7E`NaZ6k>?1zlv1idXk$rlq-!NEaKn;rtGS(+K= zR7;kXYa&$~V7;Q0vXKX{`P0DU17}>jYKSGc;GjIR6xv@MzKP0~{Iyio>ynx~3s|ok z&J}V3rhd><_ByhP9Mvlrf}C~mz62|LQ2*qC4G=tYlH4s`EUTXW74vDoHZj`HWBBrO zJT<;Vga0~S`|D5=C`mPW-P?-n>JzkBHe>u`Fx^yXR14fZ?>ROEedRV}%kZbcjtzwa zA>kD{<#+U$^Eg*&7q8$XPlSh#0@=Vz9fpDo=@v~&$clmOu#>u>V-i>Dssr4@Q*!!i ze*SypiFr@3BPrxIiJoZz=E6OLw+;9*jjh|ZB%keNBqByV{T4PV*5C!QRpn~Uy z0#O;p+4{ro;vi0Xm|cK}QG^lF+_7{Br$j)H@+{D8MsHBWRfgy6@~gb)4M!PYIMeUN8y{lb6xV~M>miIQ0 z*}5VMiD5vGG$#Ur8PPLfFQe2xiEiYjBCam;C^~rBbBHrlk<)tEz2^{~KcPh)>cH(` zXhiCY6p$yzUD2G4e>w-?s#B|HVsv!YQO?owLI%Ugs~Z1+t#sgW0$l%5gN94ltX|=U zDjNLKXmRd+=+l8@LRIETwEXAXNIH&$g34eBCl_aI%OYMXj5Xu*BWtXDTkT(9A&F0) zD-*HML2%+fqf}2r3zxu*nkLaY?5TmL^ineO95}RG#R-WK`PA>UqB?Ufl^gj&D+nEm8x zCob|^SyaFl`gPpOHI1+LnJ~q5QA3~07k*KrZghJfE0$x9Zo}%;@IV>hi3^W@fU+x8EQjL z7V%JnQopixo<#Waj35Sp!Z%;xukI^D(t%%isYe9^k)E{!uKe&3p@H48W5?S4_ut=+ zA3qLaV%yq*0|(l7zVn^@_R^ku>Zx|$efQ-%$9CYRYp|Am`st^0UCj9Vbs1ewvr;J& zq%c>Z^^TsQmfCxdkCMRI>40SF0D{*RZzuYH`O0Mbow2_57yJ$r{RvALiDoqn^7?fZ zh=MH#4rhskG#qgVdTiouYGYTCJ=>m?)tS4Gr?3p1p}Y?}3sGGq*C#YcdC! zlR1S0uyIiVRUEtaIbtAEfD)!Y9ICXdSaAwESn!NutF-0?1}Cdb&=1pSXjX|Dfh9LC z1kPap1a?)?Wc9$~^7J2j3wfdyq0e`K{v;)VOdqY-nxH>5E zHDCOr!%}{6jHY!B3i#=kcA^K{nD5%N1T1v{eGeS*lBen^r=$(~%mDn#u!a=^CGyqesB7NUhP>@>ZE>O958UcC zs?q2TA9uL6hi3;+KG>+2oYg_=gexsxYb z`moZ;1gAg9-%SPPq{4jgA~|kaMn2Ku?77J9VB_! zKvpoMF1fOfG|B~BcxK_Vb10P`dp!8ygYCWVeQ&#qC88cPGc)bZJMU~KPo8Y=fB*Z_ zK$!j~fAS~IhQs&!<{iJJedt3UYTx_b_u7X){NeV%0}nu85oi9>%1oNUKpdFEATTPb z54ROEa)6025TOZ3xBONBQ+cL06*8yAk3BhO-`88s*_DA2>o|xv#5= z(W@B2{>-jRy_g{HlK)k52r@g!r`DS14fAk7{eSIW@4IW z100qcA{rYZ3LWMB8huPi`%;@B8kvj_OJO1>U;$Dh6Op72&LtW=bru<}0GHZ6L1UtB z9RNL=OGyU#?eW@qI zbKsjsLm0ph3pH32R9KOvD9b-!)PeGwkrc_vjw+3r+NO@UbHGTOA^?d_ z#2^6|`AEO=R|l0p`So@vuKaM5zR%&1*KgX<-j9qwGJC2$h#k}u8ucmyHz2eJsspmq z;d4yZTQRx42jBr$Y}D&Mdbhi`ZfVo=HtyPp57=@fpXEK|n~eZefkztQ@VxjOr>~6A znHn2s_zwe8F2*zVg6!QY4p$B4wXV`9@1sY`78;>jy{+Z$v_%&8@0yLRnr>v@6G!Gj0ezJ2@JAOG~1`;v45ULj|VtJB|%^im@nxPV)tPcfp`# z1UgMb__H|pg9|L1#d!~qLs*eFQHFff7^nmobSX7F^%;6qk_?-{_|ws&-BPculc+<* zb;U+GW6l8__eWE|W)u?|gwGI$3WB#7rdN{pNn^DdX@)~Ur0BF!(Q)fUxF_AeMf!QmP^rv*;bM@_IojvE#0q`^RZIf3>7?~2S>+w@q0@K13{aoTFj!#QUQLiNh>qTZ zQ+OJCxDeSdfbS?hR0E1W=5A(P(oJJ946TW@l>_;02$XpmMeuhFeaA+g;zlV?>A=Y0 zUxsb`Aw|8+N-}Utv>M$ZiR{JKVgb#2K_^(>WSra zhJv5ik9uSVEhBNMYIZ+j{k{wBTjRXe4y83bW)6d9@^I#!T`L2>NxGXVtrafr=B zx`kx*(WQ=VFbSOW%7YV_kXQI|-r!bS#)!K(3d3`4K&r&WdIBi-A-Yhr!Q=+T$nPAG zLy*9>Q8Upx$m{&j*zhQ$#SG=MRZeaA$UW#t&&ohY;X`Be8$Ku}gO36(eDIQ=d|Vk^ zc4pwg_q1OU$1{IW-H$#b-osu;ua$d#&X0cdqwS`fZc5Ud-~8q_%cS3X-t(UJ@WT)1 z`G^f0Hss#Ab?fpg3}G&?#Pwqz`xrfwkF7^u$L7Fn1`-kX8pmgR~ht9G0} zi83n?g;oYAJ~cM0+Nd0*v6gs67vs#o|1Mhb9d6CWF&z0-_|)78IP-~9V`WSBG)s!GjoNJR z3zs-9@bc;ZM1#^J4`?jJ7s~iZpS*<*_>dt9c&=fjaUh?Kf?J#+N%s`LEFAkvxSQ%#HY8gBy zMh4ok>4o-{N1g>=NdXA_wPzMyQ(9g{&N?#n-|c(AMx?7p32zOU$j9xZNs~Bh1E$Gq zZBi`cmy@|gv^pa8TuWgb9`lgomDflyyroE-U)!{~t=qJ{&FeuPlT$i{QI9P?8lrZY)oNSz`!3`QSC(T88*4}9PQ?bcgwZI@kk83+@7z53O;zk?^B z|N5{0y8Xs){00-fDmCIJoN!pH*B}Y&t#5rR09-($zr#JWx;WE?1>Q5;F~G6wQ%<hSw z)LE4=M^z{v`QTLYYdz-g36xB73~z@m%2hp@H0x)JXF3iswkaxk}gAC z5u^sH>^0eFd7sJ-OW-34h=Q*%k21@qiu(Unet@8_nJ?Rf}?t9cCOOkqf1#@ z9W0@rdy5$E5RDM+hD{vmKx7HsFYTLdfAh?f?aLcBu}por-F#qg+rHyco)4nYCa1%c zF5eXTkn>us3}ZIBJ@wo^9!lLuo(q}t;xu{lt$q@s2BHU6z>LBp=>blMC%FD;v zEf0UGJ$v~L?Tdq3+MBv(O3bfwR%ex`9F-Q50;6=b?nhr^59nudkX!k9ZoQnAOq8Ps z_ySA#8G_QuYX1tD%+*_vQ2s>NApv+e$oWaK%(Di60*Aekw{V_h3y>4&<2HIs?nwur zlnd{K7tGP6;3YsDv@q3c^vKM`_Y~_DC%q1zZ_z$#bO780ADbs=BTIU~JS(}T7hmtNX7Z{FOF96r*Xc;bn6 z2M@Vjd+oKUp|`Bp#=~QX`GRafr*ftBr2n8((ZLvuc zU{XK|$KduJ&YvdgeEjQow!e93U;7Hi=9oAeQ?)~LLv4;{QYIL3N`rz9#aN>Z3f-!y zk2*7)AzX4W9Z$f?Lz4`_vAPx?i?YxN>TGkQJUQi)2^u8IHQ+Fg>d1W?+wcvV-Et!1 z>M3ju9^A({8ZCL9NRghCLnsb$zhH`IH0Vy-mY0L>3=_`8h!f?Buh-?qY+CXz3V0mZ zDG+jKY&ABmQ=-u{jWYRmjZuOFhQ~?=bW-Bx1h`I1nN>aY0xR(^IM!_MK}>qId-rtI zMx&&=F{tZJSnCkI4Bh-wCN-2J6!<|e#5V?Jdk^oCLt9+-saJMoj>sYk^%Pj}%!r*5 zGy?2wkOl!>qNi)@hj!}TJKDnKFX7oHc1(EetJ&7exZ_L8|D$FH1qmi!d`Iuu}mi&@jVi0mcQyucy9e6-=MSFQ?6`sA&O zMtK~Py~Y}*MGlGyp46P77xp5o)K*QouC!}7dnJL|`7eD-d+Ergb_05(uhzMVE5M;m z*<#P?CHcc=PXgb1h3SD)j6TU=C1-Jg1q@fxA-&c+K77yD+MYv?wJ*@?c4#qv0iO7LQ0lctTdsV8(?b*6iyqN43~%`ft>za2 zaFa$0h=(MIg>Vu)MsP)xy5qeu0_%uSf`Au%Q=u|aLoDCXn8s3=eBfP1E);xnkz3yL zk@S*-_s-4{cK;yg2#7={zn3tAjl9;=331PzA8g|X?`Uf$?`wN- zJd2}atnRT+RV88vmJzR!wA89h(1J7`o|$diKls* zdU9q1Qgm8aMs9ftm&#@%0&Q_JO4i#Huz}PmM!8i!j`RvJ0I?wh|HE0--lx>Z+fyeO z8GaL{xoLob(bEeNtzkzY!gCD6At(Nkm#;Fh!Lu&O;#&U{2P7XfuE>>q{AxM2H)+Vt z2h@DUJwt+mgihu&(o50dX?T{Uf)uf_Q58t<=Yg*zYT&@O0WF6hyoy|%M7R7J!;L9V zKoXV#x}tqS01%z&Bf@7b1T>3-w8{$_XR9YqmM(**y6kIlT^8;Bx5s!v*Zw#Dvv%{| zue4obOYO)Fzsz1^_7W<}H?m)GQ3P^zb3liY9QtFGHm}fS6H9WsldkhNA}M*wf0=02 zYf#}3C-JfIK!!xo<#eIc#zz5AGQ1LZc_zX96#n3&ob08O&$*rO5C>ZUU#t-}s#n6& z?(FR#MH3h^2OK%$*X|I`{Z3nNow0(m4hu5$X z=4JG-7DXYDg{_TMfF&-}#7$`;4VZa$h-}KphYZ)Sug*9_hcszZC8VOd4~7SC1LV+0 zR#3JBF$!;=J#ms1ozkErl!Mwz&ID8?+`hbBiPG#!GgzZ$7%OUA|>ko54T>g2thu zF+{1c==@NO44F{A9GjM@s-Zd#tt!yA6NUG?QZ zZ2NBeLn6onZSJ{8+hf!-wE9f@^yG!@clVxX!`m-vvkZZ$Rz{B*LNQv`b1GlpI}STA zw%k64V(-T@Kg_CcGS|S4vxN7esnK@-Q8t`pZ=PY4)j1|Ndeebu$o3e}St3jMr7{DL z-%O0cf7M>V#4@h$J?b5;%u9sPq(QgYva05{| z3+b019c*}F13StptZ7yu?LD^AUVG66?OujSZdpIsuGw%MPp1#IFE0PC5_N<*qy<9T?kOSrgABTCPW73KISoUdsS>di3z& zHnMJ_t>Zl=G=r(38iKB}z=nt7ohaY3!qs-G#~+H7bL@_+K@^%Cp^#_lF8FZ57n)H7 zeL#Yk?wrEFMXh~(fsFvZp@KJyk)Qix1Z09(t|)lD002M$Nkl})Wj7?9r9*0d z2I=eOI(fB!x0doSMAruKin{o;T7 z_ifuV{q485y{i4*U;cT!;_G*_Qir-#2;fU=j8liEeo2>#%OvZzr@q#HkG-kd&A-^* z{NMjq+jIRZ+Ukb&v=`)c6+Jc@sO^rR#^;>3D@r-Ykp5r5au^hti8@2j3fB!zbmFh9p*kgOkT9 zuzbVdtS`&2A=pV$8w|y)D63JZG?iW>(%|bMN;8Qczsg43Edgz=Qqov#1N)eeXC?ud zN?3hThU5&-InnvYn9KNvhJ=M~r#|VTY#K)S0dLc|2(5;PvU*8zrB3OWf9L}C;L>0D zyGYkzD{sH+8$gf2??pJ*OQHQZ+r_M+Z?9(mvCTB-&P8M~2M%86Ruj!?7kolq4ofkk z5+_*tN>v>4QC+q1l5q)o!k8NF?PG}5NZEOgS`@)KAk`K2y$M|HLnoBsFm9z0hX-zU zSn>iswHVmhoA)8N*mD4>aXj0k=}^;<4FwOY#V64CY}~XV`>1*C!W89KJ_3m7iTu{S zLK6>rrlF=b*RqiakH91HpaKt-BQ^-F;3Do7Z008a)GMGVG5}<#GtjxD9+$~ND(9)1 z6JKcE*AB6@&|Ns}>z9tSw=ExPpJwNaebA9*$AM|R$elxUqEA$k9jQe$Joh)Xk`6Z{ zj|Nxucc+4EXxJEf27m!fHQ=IaM)-o=RU^xI2Zi&@58F8%U!oV{92`1Qc?E$!sIQbQ zeeH;XU$G)#!t?kp`M_oDgu3%mJ>`Fvj-Low0u-Fz5Cam=zy85A@AZzqS2j%V-Ff~m z!lw=ew2IO6^^YNRx0O)qVFcoK8EPhp>@A=GMg(e1IYBwaVe2b1$!jU~&S>J~e1CiK z_AA;$Cm$l3rfZEh<+YY-l@5xpHz22 z*l~J+NJYcxoUMHDI(|>uXhc0?#PHq;XD&jocnuLr9w#E&we4b<2%V%2(y%>uaDjQAX*jfim=Sf28yboLq@@b-ktoltjjPD@rnU=LqY0i*{gTtuiSFerO zT^f#t)1x3Sy_ZvcY~9X7s7wvElXLSdM4?Fxjc`FWL9_MbO^y7Di+IUJYtU&KUE3ol z8zKX}6-y=%BBT0}VYql1@hQh0IN%f;!=Ys!#NLegkcabnGRijq_MBK6BU*2Ja`p`J zU>uGtazZD<*vLI@u7hQrz?1)+NmDK5SC^Fua)lZQ0ggGe_sVH*)5*jcA9PDta@dwK z)?rbqh@2=F9+8Wisnfj!<_Tb~M+a_UZtA*iSF|^9ZV$ay2wIoHOxdV(Wn;$TFtWEE z?t#Dai#6Y8h2mCnLb%wRG=dv&vPDn$t&X6d(ge?9$!f?UHAqFcAC=L;X>beH7qn>V&i>(P7RIw zMB#~Y=|zZp976Ew_#MLnM{r0aP8N+!!ha*1>{rpe}T>Yv~G!T+3 z9I_;?QNOI+twC^zs2%s9n|fg@!gStp1? zuZ=}E@1!nwey-t^atdiAz~9DG3Zfg~fenZoFd_#PJ%2K!oGe+w7t5F;4hA$noEF(3 zv&{dcyu}kJ+ zDtyWTTzt}-^_DCIhz*qG;0&cWRrY|2ZmHb%q~?Q=>eS!K8p zde0|kR{W*7^?Uf-5WP8rpi~4-`XlHs=dC`fJj$00)FVgz1B{jDxA%t7>OIkOR`-By zqb^ZObBR%cjgwY#;p9AD}7VO$Y7`4dqXRVYiqh)slG9UWXFt{N^f4dd5&uy6O{zxwvJ z#QR6aP8@BAuDYZhe$8v!&av(7U*COCyL@J_{hv>MyuJL%=h`)QewLT$>2!o|%_^I; zu&gkId(IU&?wDi(`xkC)tJ`1KUc9=#ZQp%$d*)}ZXAT7V88WhrikA%-p)=4hbSHx~ z$js~L9nFW5I-C&4NEwaXD~&I7+JnG&ql8J}K89^ae~Hy)As;9efw4*?2CYMt&k-1G zryevu4LzWptaoT74pW#AEhd%-?~P_HgCToe!&4d)ID>Z_7kEbb$yK;o7x&2{+(fv- zOQTS9<(^I>szCkJ>uZIb;4J3`Ozy>&fFo^rfQ~W_!wlM($QmMS;X8MvbMRrhXp?Zk z&!LDCBaQ$X5J4y#0z4#o`wXpt9eFab;NxI~% zL74KiXTt$~)p*vqNoV8eQFu0B|AR0UmLJtE4oAf?`hmlVgu)crA+uB{bE1qM#s4_c z7!gA7O>jw8A!V$Jj^KuIe7@jm4ktWJ9d0t{i=@gh6fAjlb=zhmQvlsB!zo#hJEY+Y8$MU;C$R`-ZLU*8mlF64yEzSakg6L0> zr-#F-4Wsnx^n-WG0E3j+CI2m{Q`K&lwJp-ZpU6XAaw6{BS@V^|!ql0P(YYIC9sLUq zG}J%wqD=fmrwf+v>E$Wsm8`d$LsaL5+MCPq-0T@T{r|+hS+Hi;b>Fx8&O_hZ z6M8^58bCKj0-OL&pg0O7Ba#wjQFbM%I7!L0R8;YUB#W-1 zW4WZ5Do!E^jv6=vATbXOG#Wh*x2HSbo8RxhzOPByWSI}Vg>#4ReEaOZ_L}$F<1t@! zIv`_%%BX#vdc|w7!O^{Imlv-v&xBruCrw`h6?iiAX*p3N71)4UkFr5mY*?&|XW93o z{;CLDbji`z^oqj)1A79lromRDWEtm;1+=qHq-<-f{KR;G`I!48NbkemRBE52NMzDr z0u3XdW3!)c2#gVu>I7kwUO@b+2ASN&QP~}dF(C*=tmA59vSyu~3U0cQr7Oayhp5MK z<28nwUVY%f?Dg4Xsqd=u2M<0FJr*ZG0dpu+;o-WZK@1owu%0Md6b|*`Q}E(vVZXMT zT0h13bI>U_!O3(oq15)sJQ>)-7ln|w!R1b)5DMkczIGfKV>A)2K*usmwtX#cPK?E= zryCAGW2{;Fs-hZI150)&8tca>)^i-cxSC&{tN2drk|}iX0S~+nFLA1IPQcF?G5mL& zIL<5z(y-)>C{hbQ%19W}N;(gEKFDXJTkqicFzPmM4PdtrLInv>y=;35oDZMhd&bv;0B`K$&@g_=+rl1awi1A%Fx?thCm_QNP z%kj0s`#83J$J|uSoqejFd@ljVv(MDx3*RFup$t59nALh4XXxiPmTC75p3x=bM^=DBEBPH-)JC8~m^vtV zt_*GbJ&L7qwNmcWcv+XWBZmbh?mEZ*)|?TJx2K3*qF=18Y|F{8zpMq3cX}m;0J`T8|Ui%Z> zZpYn?lKLS{;FQK$UQtGJEE__kF{Fo=Y50KU+@gG+pXLN+UPoc0R(wjILyK}vW+@qM zCoq)K00$r2(7BRN+?A;w4v)ll<*_vvSiW{wB7Y3u?r3^uD7@pLdRxb-n-k3p6~4Q= zFb%~eRgMi9DU)C;W4W6i^`UZMunygkM~T>~>zouU1yme9GSRAVh0AvyQXVDr zp$^8zA&PI63a#r`FbXM3Nbx8g0w3oc4#M)~H8w`0oKo6|N~;Pz`0BiX-}y_m8nzHs zQ0m_1t3<)p%k(%+zk|adkvbjI7hf}pZ$HwRXSEOf&2c1@_9sSZLqNH1sKndv{FwtH zG=>>AVD984^P^dv(~Wc*)`3A=zE{*xiWN{pSj{>W96rTp#0Oad-Q#D{D~SXepWq8U zqMqfD$lWN?h#w&NQ{M92p&X6YCNunj=#CuQ0cM=ty6lN6C!Xhc5@m-B<&`u_pH4%^ zV?Ldf4P2bnpwdK>=!-gFr0W@EI`gRsHd)eZ2%R(x@>7QudQ;eCxUlpE;F2uFZ?Vf(%H(Bpqnd+(tKJ9}p>E=hxYq>ktx zc*+xMjSBLqt;nx50ycO|%sb2xdodrfPGoMtL?KWht$Cka*U)FfzRroT_EhETm}yo+ zG^=eGh{z9>%#caBX0;#i;?jK8M->k;*r1-lD!Nb8C+&zg1E~`7c7VXza7-L zN|Zfo^g5q>?s#3;xKy*e8UktZ+&axWwEKY`5VW{*%zC3I$9UJ;l)Q-E&NQL#u@v5az@?36(&reoC}!)3#RT2WF}wIq?~6))qhTC(!y?xL4ZTy`6C9*ELF0KvCzS?BZB&1h zAJy~IIC&>0T3OaN7Kcxh@5jR}&=BhRV3;qm;(K~>;Hh55=_v%#)1xF!eMxWGvcvDt zF7-O^tP-`+14Pedp0T>cpuN zyb?lRB)`R99frr;$5F|8zPr3$*|bgbT;Rc`>AKPQkh&F@Tzt(gm0qW%U1XRciYj+K zwt>VvkD7#8I`%{-ss;+w?W~1$P^wQf4?z&Qb8B85ykm<~qtmQVGFm|~1+7ze1&xhP zs!}-33gO1V@jcFhtZ9gNoM_!_3R77EI}R`Pq13FV(1~jtJ%nH(X)ujqDho<#H#bTr z6QnzJTgE&p3uSNyX&}h&Qs%~w{FaQ7pj;?jAX6}ksiKL$b>cL`Bl0$Uq1zx*H#iE( zg$6!!KSSu?xfA>2>sMI%d?rm;oP3;>QM^1Mss{WttA|E38p#_t^EelWElg}w8y6Z} z4L$4M!Oa{Rs7Q;R?jVlb2H2K7frLgUUVLW31O_(XNnXn;5?oT1%Y_#5SBH_|3c%x7 z#LYbUlSw$%Ztz@P!}G|?qEU?GR8BTpLB4cE2H!jM>tMoik{{MJ-_drIL#@!!cY&SlOWXtRkp+LWVIt_Lqj+FiIhtMMQi29)2hQh z>cWl_pv9k%E-G`r41cB%ldBaRe-Gg+m` z@_rBUyZ$!M!?Cw)GrULtDo~tr;5?H5XH~u<^T`B26q!wAQg*59wbT$DC&Zyj;0td*8x)Y z8n0$OuwxH+?%=)eqy-%aL`bT04A7wx`2lS@9wTyVj_mlKaenClCut6^QTWIhGL4*U zC`KIqA)h6E7M|%4NQogrgY;Ra^E%n-oBWhR{yDmuWwlfgwjT#Qt?*SH_u^NqAOdZ5y?hXJ&t;-(HOX%)=)#NdA;32M9P+a)Fgs(__2q z$o+TLi)Su!;Noaa-Mj~SX9kZuC^TB|8 zTBm=?GYNdlsxfWACo+`}Cx>~iO-0+i7YOB&9(_=DQZ#6E$(a>F>_@qqJwK1X_(W=h5jt}ubAt~Z|Qn)+_N;(#;bQyjHPx#$p+jc|e! zaL$ry5Vip`hL;Ta({p3wQ+JT7niRr(Hk{ue&HJ3t?J+i%r(qXD5AF4aNHwV&U7i43 zXzhS)Owa*_EOY)GbC-{wJjNli^hVMbQ-{R~hN_}s5xeIu{1zwp32nj8dPs8dLyo_R zv13{Ipypls0_l@C@qxmJuMX$w?>E*sjey2}Lbw_;4PbVtqc45jj1@P8=%6y@FTF#PJsj(ppKYp4NR0Q%Y4 zXwXJtD!<NunO{=Pyo|hJBMBp}jg$2sA%~ zlMo0J@T_U1#FxJ|1Wmp1wjno%rF&??ban)wN15{ySEDY&)W8rf`NJdi2uDN2Denmj zc-G~OLLEOAWgx&FLqr=mSBa5}Z2g>X!QBAEd!t};pvT_p5cofhetdJ0&4{<%Uw?1u zh1xiEs=l$~jtq?*njWk9alOQ#hZ9W1#ZzKeIjwiGVG9$-shiadVhZxwC^Nmhkd=;1 z0v%-vV_5}SY7kh{X3+g_2rD2j|&@-?dowSW|>x?jkukOj2ZtQwUTHJ=ZBbJmQMW?T=$=e}b zM*eGTa1~bOHw4Q#qklq|{zUxH?*Lr5nR?AA7CRlTpSTwM9AYr_~&^$ z;M^ShGNyK+D?0uG&*V`Y6g4r*(E*xi&>mca$q;3-f*<(U)5$J&{e_sJR|bUka@W|D zn%=HyJUxqCKZ9LPGo*Ow%&YbNiOqTjw8zjV?c@e;AfX#S>Q4_OoC7cGkf-8;T*Nnl z8#HXe)mpm+fPX%as1Li(HFVG7kp()yj7)@H(p4nVT=FMNG^RuXJ~u(dK}ch~70^pD zWY|aHp|He7c|GeiOC{GRy+| z+lD{I0|0ipkH+V)3p|AzBXj=69;A*8KKfiW3hnlK!fR=g7fc}Q45&B9=0|?=mB*9_ z+o<0nDMuN3EMI+|6Nt@O0TY zv)9(@@-nNhX8B{&p~GywLa2K$I?`ULMdS=&93H}J1K=Gn6KqMW_!>lnBRWqx11!oK zWcf`ZB%^JDG@YhEaK;7~%Gq9yL4b{ER@^aHa%unJ-WdDxBem<`f%?w+3>fI#m9G}n z)cZXR0_EF|x_RMr;+B zX}}Byq!PaS&Y(W4OE`lD%*vIYdKCWgDAgz@`GA%ptgJw!eseBd1r|L32DUZ}7LCxTWY{_>+Cljtdy&%@EbAR_t8M;_;R`K9_Q zFLqiGw0{ayMpLf4mVBxVj6DV>!t6KqDG1@~tYn!=uOWefV-Z|E)~eDXQ2$8Vq$M0< zP5x?HMCy_rArGGC7S+f+28JLa)??lFDm-f_=;iTK=W3pQeX`#u%Z-jgXuuS+ z7(Y%<*lEPD5P8{AIXPsXX*}nSPbjb;pZ()=c?3`8Q{c)=U@8;gQc6eQq_X1P#+bM@ z+Nj1!^52S*mS{6(M|pcBF1J>8UBV&8EFmVvWvw~QwI2l^lDP(QY~%scJ%W}|2qLe# zA_aU0zCKUglghp5ox$rR-v6<*JYH`(bZ7meH?u)-{bGIN<;Us|o;g-$5X(5x@eC&% z_#TlpojTCmM3{GKGJJb>X_O;sY($tFv{wRb11NK~2r*Yy$1>~H?1wI!h6YS@C_;%_Crz{8?4G0o@^;0L_2QLB__-57- zyIM2L^W~lU0Kg&iQBDV~jVO)4=7~Q&oM&ag1>fLoI+>O^n&_q-yJ|Zxfpb5`rc2hr zHvu5Hc0k+E90tz{Jie--y%<2a)HyJi?wwj*8Vn48;qV&unuXdez%SpG2@3O80=CDl zOx3?$-d-POZf2T>c0KcUG&n3MO5;UYZNBRg28H80Y@p>4N$_li#Q{C4!uKt>Y*Js_ z3uY>CiFG_SQKiQ}%bYk>d10g?lM#ZE{?Iun0MhJ9xbkIG2NuTJFklQQ)#3GP^@R)9 z>e}SBy2Q(petqFu9oX(+i@*v@Ygf@wdJNIk778dsK|YTHilB<8kh+|!k1`2o@4;?8 z>2Xgek4~9?t$g6_`#{!F9$|DI-&unCdFYXf6{Hn~_)~|MRcna$NQ@Z4Q4YmPQRfQ1 zDc`>?KE2A!TPTX-B{z5&-Lyj&gaF9{JaM%>5*z=93jwUf#!m(G1m3GFuhgycw}7j% zpc>@?Pc!)qJ!wpF0>ex8B=RG3M?T%dN~B0l?!9Tp4>;iY&yNHFjUHd917pbp%5)Ck zX~Z!y;5Z)@r)kfDBG8h4Yg)@(QG_dW?nm&acQ{ z(?lSRpLxA+YbV(#QXM1b)@c`TdjNF zw7*`xe7;_}euklv9lQf(gENq-c28pT5OtPwutt~0>&)5bs?PtgF7VdB9Xsx;yXWV? zpCK+~_>(e_5$H!EP}BL528V%jLIZd&(TI-ISW1u4yz?>F7TLTui6ca|(&Q00Z$aG7 zRa{*G9_8LUwowc231U^yh)ZUpKfqVt)Jbo>vr7NzK)D>c%+7YP>&l~IVRd^x{^0=DI1~xhjqOI#dNe>tNGeQspAh!jq zfe#AD7Q~-4lQWm0p26D;0peqVgN-J^-=kBL_2#LIbztdSeQD};Uem3{=~oPzZMeY& z9?LWBJH5cjU(Q&swA%|OILZ8n`L^cTI>Wddj{llf$4GR3D5%Pnc@q?l2bs)F8nkaRMsH1CzFUH7x!@lN*c0$bYmfz^-`g1C zj3twDz4RF_0$b9(EW5}?!aMifS2xWc#PQ&mNmFqf59@2{Jn2`bqoKy&gMoAcPr6ig z97*gCey73r(;ppxMgTv9IS9ZV5c4Vb^3bXVc=3c=d1InOPx4f}!Np=eic_~dlh8a? zo{Q0KJ@sXLDpIEJb^P?wfk@}Ba+Qwd+Lv_!t1N1&7`P-=H&!4UDp1M%4 zJa?)7-G!>pEgY}ItDme(cidMe$9L2n^=LiKL5<_?z)r7}s#Ayba#1Cf@{7&Dv&>IH zw=z&SCz&8mJ_zzX8cfC1tGxK_0w*xf@(vgmqBtk8M)?FFz}BH}psP;v+lY9ogZ%Gd zJo%4S(_kBfWM77`qwv<`o)UX{=!UXM?#3W89LusS`568WJ#pheK|8BwjR<8_X}Xbu5iAej>wq5Nb7{LLIaQiH&{4Qlg7}dIS%* z;EHIr0PfEXvRmgKvcNiFfXax_9U?+d>Xe^4h!zZj5Nn*4j>FSr9VVLetSK1>Bl}(3 zlm*t>=uEA=|3fwV)c0zJNc!Rfck;SWBUb?e3+;>oX!z9M7jWV*_^wo(gd<;GyGrHu zI1+`bG&<#sMT2|P3r9{{XgQ0DY_A}}knEX|x=vk@aq31H#M#Dx0!A5bd`epILuE7H zqGH-$Fj&JdpSztqvH~}FrDE&L^nmSTxdmP(tl`7(w9yP7cq}|lLI>(}=o(S!8gJW+ zdSVo;+n^4fG9<7ZC#?Yy>4;`{p1v9qjd znZW^?mOUHf4R&FacVg-%{E{~A;xKfG(jbo1VJUli20d0C{eYKYxTJd_C-X~}!z@c| zB-}GKU-$0@25CE(n7*`nJx*CS>=Mz}@7`07eEReC`Oo}sHT&5=U;^UDy;1IOX587^H;1;GcaS)NMoV1t$E-Q|Vw~3G^SmX$Orb z8$lD56G*_TKq61{7P>vOaF(HzQPM8(j*}^z{W$Pf*~<}ru>Zs*wpsNwWZx1x%j(h|(c96La;ZkqUp?S|=JInCV`MnKJN7$;%8eg+3j8J&;c_1+9*AMti z24QaYn2ZLjmX9`p9H-EKI@jOuLGvk31VqN{&!WMBP{`(|qa4LVI% z(a~gw96Z6Q*YsO%*a#T9$`YS8JXaa0dbG?N{NO;tv^G_WY1IJ9wsKR@ePGcHUO-^~6| zI8IgL6;LPTL!Pr^_BgwwuU|e{S1-R@r>?Iuv^Q3}c!T3(r%q#}&Yu7=_)0g*3vRwE zAuHsNnfnU*=xS@!xr+_=Hc-|b-pSX-S-xfSr42!-z&YUH+Fe^7UX#bQ?|hI0dL7#i zBOHcm-fHj^N?wg~Q^V%F5r4Y()a?ZO89q-guGFJXoTwL%U#Qdc(#`?H-PRf2(fFv+ z!w1E+qg?9+C-Ugflwn7i(j!J@1p^Qi1-*<;Bs@-I&pq_V`=s}(OjsU`Zaj1BZ2j5O zFV!eJ{l}vla)G72IEWJ$I6Zp$ME&+}y}s@q{{r(SXX_Ae1HJt6g?jd-7pmsY@NRYC zLL)kyx|y)gn<%M4_h+3g0eV{Gi~W#hy1_&1nb>U03&ULE>E4G`=1_FXnNY_`D>{2| zVKK0mSjFh;p(oMBQ;P(5aNz)*Xd`ePu5%|kc6HK{DF=`qYHQt5X3-HJlFh{u&Jh_w z^C*duCXDEYfh2}xFVEh%n@4kUFmkmPM;LMuC}jyV@WX;=XpzW!|Bx6tQv^&8@1iFn9t<qk`*X(M(sB9{?(KXHq0d!OV;RxLC3OAE>10!{8HE9Z0 zMNw#q-k}MYN!uQnzNGXX4D9~ZYqdPGlV@Y~eh#_YgTPTa@;Ihxd56LH3%g+lT*ua9 zfUOYVNSc3C7{R2$Q^8{}ZM2yjGpZq9N6+L%us#0Ty$2hqj7jUDWPpxO`i*jQD12LI zABH_39akKy2F3kKTBSA`ou-b%$zbz&e1a5(Ur}hIP5IYO#1|Sl3awLdiK|L^l6QxE zd*Vv1E z)a|>E)SrIuYJKSq|7+cL`vdjC=RZ}e`{rx&{&&}fI#@StKL99t4ADDrgX8Kwyy*sy zs^N9l4`3?L8E3*9TH^0PzU5`_Xtb6Xnp%M`+L-u@xAeq`D>tBwg6lGcXPAH5Gqzga zedT05!5j4Uq9e+!rLA1ZOi5!aWh5#IYP{#qqv=DmKd<7!Uv_~XP&nLl< z@&~u@4VikiPNpXO1egHVKLg1G8v|Ruw7`N08j1u`5GF^PKX8i*7qRm_SH4o$o_(r5 zJ^Q-))6w_UUtYgn&wuqh?EdGyAm9*`Es4%YCgROyz4n+~hE2^NGmts=`leNepd%A1 z6sP2|;4k=+g|FcyaiCxX3S(SSszNAP6~z~$M49z=ZkW<(=}7D@BiJ7stwN_OiSV-* z0EQVs#A)$lokqg4>pWkNhRRNrMZWhOf`%AnPTjB`W%Y{r>9`bvf9fBa8{r{9*LtgZ zMi-;t>2Ol3RvV)bx~HNplSX-sDEl~KK;Y#vLSS_L{3jxG{5480f4o3p3}x(yPS`du zU}v#d63Q(J;^#lUXgX$a9sD@F-GMR9PEIrS&7t@h&?&iZMVnM&x6cKm4!;6gsf2Cp_Q@b=hT(_fU3}Q(lgjD_)8XA093;_dKw(PB=x#19$tWRSQlElM$%z=z)=!{T>A47syuEdGXAP*a*o$RT& z^)J3uOCR`0wf?|6>Mdso8FyrpO^6*FN(5=5&AG`bh z`V9ufZ#wjm`Y`8e-FC+>)#S`Oi26BKD|21S#kw52r6-NP%ZE)j(uD!`hG|H}M@@(x zz-#!@`0$C&c^=lBCp5@MsTCzBtJQVN_~C1$)p6@vWS(Sta<1OJcc%W`V_)OY4krJx zy$Ku@F*kTj)0?A*2b?%Ahfc(-Y zztsV3&Gl*`9C}DI{RVB~Zdu{ALWGtc+QcD?AI}^X6}v3Mb$E>4izU_1$TCm$P4Y;) zlN0=loVJlPXNZF#Ph%RT=02cP&ud`urH<0CRA z4V`gN`<{?t@``_yuM?U5&H;UXK>D1RLu?!WTgx&{uXckQdauRc|aS5C4=VUgn$ zrs^s*x^%SPye3};$|^0Qk%puB(G3E0(#1#vOJf1C#Qy9{_VejK3V0Q5|H0pYXu}z8UW^uu_xTS^mln z@Mq3ks28p;;i$I*OYX}nc}pz-=odp>x1#Vbb6(*m{Wj*D)PW4u$pgzX4AQ|5wNl2q zqc7ANI&?tl0WeIrDujabU*fPusJD7x$Wfh|!$(f^kB!dN8;=~RH^8T5%I%!nS@+Bz zuJ!#v`sZtGNO^yPpm;3d6sM;STS5}wjX=vRGNfW1?P zR=j*43|YBwn4!t+@&1qMHS?pXaF z8w<5O_nrF2Hy^35vIpS8)GhVWeIKuf)=$^RANzEjc-=eeM;f-zIc!@H0Hn@sZC~lhod5=@6o-ugsDR{!nV2 zI^r}Q+*^n8A}8OdU+3wa`2v;4!WTEB!ToV|Q%t_?{q?s`f2;ocl^5#R!6Wq|y%Z-f zEt4uD7v<1BtQaOR6J5%2`HZ8nQ4wz&EN9VDn220_;5ZYa&}h#LoD;!{uXRYf@+MEJ z<$jJOu~*nkIK$A8c)Fa^JrZu7yuuRgMf6*}T|pPFbBu^9x0dPM=?85fboh-QT-GM! z-GDzmH$Wa8UihTGc*jTd2Ogm#?L3EbfSy5RFhjoJiXAvK1%uSFmZVOSnG60dkv>1U zyPiA!YF%N;@fX-Id12eLHS*l=)%lgj>)+DI4)Vt%Dzz z$fnnA{oxxW2YSU#)5w3Rkls-i&*Z>Jd-l|`NgyZBJk7vQ&?F!70EOWYu|va_?~6P* z^XNe8luCPa;K+CD&JP(92+-3~wmC-)$ifzr967_M2HX7AchGeC>f;1Qd{sQ83CC** z{B}&iqdgDRiQC^@Z&*HA8&j{ZkFfvb|i5$kKC=%-^QzD zC)KUPEG1`wgnNXx>eAX@v#Xt~4ASUPUYVPoLZj6ypP0~N47{T#6@zisa7>r2z$wG9 zQTH@3LK=fGPlbxE!DDmT{P6oWl8T^1HpHyPMTi-UE&DuDx%6;m)^;`2#*VC{2`}N|{(faMh zg}Qb7!TR^Sdi9r9AFJsD%ndO0a&2rstCTzp1DI5(TLTdHIzJ~@fp6m-qvZo{s)I~d z+Qyf%8d4^dp--nF2>H=+1`Z|ch8r~-1Bb3WSEABP3iLI_dI9d0nLRzqWFwePIW&@2 zLCb3(b)>AkfI~S?p?&wtcpZ4_uMrJ$)|4hFeC0p`sSGlQrX%6GLy=?9W+M@}L0*{~ z%t)tL@f|%e&2Ll=3>+ZBbeN@A+mQ;4UWrB)^Z1>7YcvG2Q`7ar=@WH&ajxEea4&m8 zI4Rn>9$;s9FmR>Yc?EIonu5B~Df*islg#(&tkj{dLw;pJM?9$VfHSJEa3CzbT`+Q4 z_xKn)&zTDw9bsP)%ZYWoYIn;39$}9;>rCgFba7;i9a)U=YUlUfdV9Tm{z855+BfR6 zQ*W$q?*2s0fz$i1|ER8wzqw|id6|0pI1xYPEl!tVTZeV>;v|T(;jkwr+hafK7)aYN zX9+oZoUm8_WDR%NFXiwXUXe$NPy!nD0X?Uto(Q+~D0*pu=&3pl#~r$&KHos>Vu0wj z@(Q!nC3q0}m8k(9Sp*>>)Ds(#zo|oC(m0yl@*;D@AWr%i%P^^edjIg)@zO3-3ReGeJmTVmccs&ZjGx->s;9wzw zM^BC3s!Kk+b{9mdAYpx?HW;B}Re2O3%92fea-Z+|^~&lTT?Kj+Mrla4&_<&+Bx;e$ zqC-)DDwzre47=?{s2Y+21x5~&%bn^lFiMm{qtpIWkTz6tBAIeIb~y;NQ!(1Np~+cE zG-jS>gw^q<@`{S}0}Nbp))U|Pr9N6vyQBeXv zsEI7;d za+NtFV12AE^ruiJZJPSn`?1O`Kz3@F@8- z4w}`>sQ@qKtk1fP<}t(cDscI>w&LZ|M1ulvQ@6M<&aABMjln;;VMB3{p3HV=xWvnO z&TmZD9QE%a-S@`(9*JI677Sz9aU#LOD3l03yao^Ti4Aq?LtICOeV|)>A|IZ`+3_*> z9$K0i!8J-pL*?+~m8Io+^KD0JY-OR2EnKeyGoDx=Z!9m&uBX3(tID4q0guR%|27nG zPlwhzqz5EWN+YI=C0F>(-HnEW%tqKe6~7IRteIvL<>Z)+hx||;+NApGhfY@Bk{_Aa zIO%j{!wP&HS)Z+Aye8q`#7p&#)syw9L-*8uYo}}f@_B~f$S(aJ7bG?qost4$Y)eN^ zkmh6VeSgvf&q>K@D+u9%6p$MNeam47nBpa#KJyYk8-MWDUXZ=+Ie9d4E@dRXex@f$ zo#q!8ZQBMry`Q8ATicR5JaZ^6{18$8NcDJ`r~$m_)s^Ozr}A7nt<0d;#XPAv<>KUd zrk@dxOY7Yu1S&O7uXt+Pbxs{vt2>U~&B6FL(eN%|7xqhmoM4B%8R9QY(NN46r7?0R)#zHmBGgQTsnn*Z zOsu?Whpkh0SfH63AwzMTp2F8T;dpMWWUxXmjc&N%sD&h6)?>6|G(xDZUIcH%i!)J( zQ5KYAlJkvXxEeqdhB6LF`<2;6f6~(e(v2%UAHGGgRZNvYe_uxmvd%!1*;DfqI&# zS-8}%6KZ$C^Pzpn2z!Y*#MS}z$KXVy#jW|1o+;l|tuzdzucDvUtc>HbbU?yWJ`N$K zEOrQwTrFunc{w$`Ub|_~KR^9zb!yLn`oi_k*F`p;etG)?wR_ByhiyF6czL3|AwPJL z*V&?9%oflHjn=~T35LF=kmF3v!5f1L3V}a)m9g~VkkyaKHt>L}16I$$wTJS^B3-&& zg<(1kc4!ZQ{<92tDNOBJx!4M$Pw@$Sq_HBWvefdWUwCq{?aHF!6rj|vGFhSNeh%bf zA$cZJ+G^-hA580p-~7JWa+2s}Vd)Y>&b-YMqgAJcB-bbn&Q!t81k0%>rgyQ#p5P4H z&7>^}Fo73*&AyQMK{!bRy;Yag|I8~+bD;3UOj)f#T2@4aQR1x-25BMrZ>9~){Wv{XbNS;1+`w?MjPe`(MzUrFCPQT8WL|Qra4H_! z5Jgk5yckQjJ1!r^A-x&y?6IhTu1J$fQCba{wBQIm8k8IVT7f}2MI3I@_|3an*KtB0 z#*l8K)C4^KC_^6q<=YR{>yGZJg+(T8iCmX)URh}bAENLmVACK9G|K#RH5!X}Ynar> z)kBCjGJf|DDyVa#~UP(?C!P-C#uPKP8gRL;IBGx(SiJ(IpQdLkS3(2=|A+s{8<=d;&B*_%Z@u}AG6 zI)M{))slJ>&Y*AGoPP;Uva9*k6}Dq@ekns}X?!e)Gl>%;zdCM%%bcOR%N<*fj)S{I zCx}uG(#W&e8@%W;&cAq)FmGSP`QLlXp1QiatM;>x=_xugF+X?ak{g3z7SVBB|a6%18ucnU)jB+2*COv&s&gF8TOJ5`6(HL_b@ zE_oR``39YGF}@}+eI^F#tGZ}CokL6wa%mIlWi}L^y?icnmg@kwS9Iet00D~oy_Ai( z^|3kHBKP$0<1?Cc$~x4KJ?WQ3bc?Q^!0>O@pb#X~2*A&wP&y(oD)@2wFkjMS)lZZ) z?Pp!GHDl(9)MSr>mRN{p>|QvIV;k|P6Gl?))LRK72TR4+5GID!iUJgdl^pS~T;>VP z90gX0B&t{{4P|vkacF}TKFUCY6^72FBM$Nc!{al=v7P-;kd%+(w985*Y5d%bq|vj4 zmiZUQdQ+`R1Z<<{AeYL(8%LJL31?`}O1^~~DjIoR8mV%pw=+~kyUEliU%K-Tt~v{y zAE_FY#BqaC_IjH`3$S$>zbPLwWcso<>me&MR`TPM@g@l=oJ} z1vJlf5zjLriP{tDJKP} zVJm?i3JR{&t0Txlg(Rq-@FqsCyeNjF>j_O^nwRucDvvZmH3#?-6dcqOZyN`lgIp#B zU>ybXBze+M>c~K|jT=1>AD${h^JiWJer0*KIw#M8y+o9`v^-kBa{pcR%!%j0aSHVS zaul8kb|%>~a+l#Pal-kE2DoIxS#HRdG%Pw5c?Ex3Fv-^qx-i99eU{nK3ooar=j3Jf z9N@vi>n15qG`*Mg)?3MzjusSD0c#w@R^j8w*lc~6p2+<>@2Pz^zrWr*f2baG8NCf} zc%}>}3r;aZ>K`TIURxcn=TE;}FYH{a|L;X!V0G-#dS-gHPOM%;`>>gzOu$LZHU6N7 zy!_lo!#BbX`2h7XKQ{2zqaLXzFp1CBz37iPn2hXg1cWO)Y^T=I&2E$oVC)4A>IG@_ zO}*(oN5Jr&vRQOMK5K9M_(TzIoRXmbA}Pc1;MVhZmTX>FgG-(#2uxQ;uhp#wI7E|1 zHD`#Wi`?`n8()++HpVk>S49;`Xats{BOdITMA1>lk#+b1{H?mQ?EmB%HUN)5{&;=$ zt6!}Tedt4V&pr3Rh|PNBkw@zM`SWqO?|ILA>fphHb&eM`eeQFgtKICHHSaiQ?wsQN z2VeNY7i$0h{Ym@Dh9t&8twwPQPKWEzh^v_#J2c4^A?@IGtQd~Qj=|})63MB{jLY{S zX0lR&A}sPLcvmYrq@p6ZmETv0ii6(~9r+X>_c-(HV*s9-)JLe0PJWf58#rz)gVE?~ z;&^n5Mulo03YItX#X!wx9&iSxv`S+pw?I2P@Dd+cLYjuF<964z$04}%R!*^ShHi2X$z_raXVCSEjRXu( zkWT5M!S&U`;;y<<32DlM`XdL4GhKs(=k##K=wfeUSG$HKpERPz$4`oq-e`s~nU~1M zPM!(N#xRW)WxaP{uH{-k+c1SKpZzD_1t`7zlGkR{Za#UfJ3Ry+hzpL9v}Dw&1c=X^ zw1p1gN-K=a7`wwbfg{c843)Z+yoDWD(iXfCVxmm=Z563Qed9JuD@oHK6^)0E<|+q% z&TgBlJNH*zxPFDxk0+QEr+z62uFdN#vY^+YJgh21vci$?_D(_*U->RQ%@1i(;*GaF zvEeYqd55KIEA`m97we6;JXF`0u2B{np`hdZIcYk9k(etpb816$_`0{(<*~Qd>yE!z zZ{7Doog4X;+6V8ET-c{9#6dbT&!?eCll&2zJmxtDy2cSPYv}DnR@F%_^+%?Zv8S(0 z^(CRm8v46~eciZvPF+7w5EAE1S*x+$Gz8sC?suwS8{sdo6L=XADP*w~N9sUd^aD0H z1~Lg?k;54HKhN2GBfJ4}{lux-F?mmY8~hH^Sj?F%b}n7=To?u_1TzeAvF4)j1$OUS z^Eo(_t%*K6#kw%qp((%aDh;jQAB4oK`{dPzE!+;cLBc20S58Pm)<@8ZRAgC`XvUj6)nM1p-ZU z62}2-zKhp7isugr17#H2KykeVrUpg{QdLsNq7k5Q%!fg)5s}*?(FoIPB3~SrJs*b_ z5PFLm$Bq%}oHU_fYQ|LqUsacKYioR>=uvLdH3~rhUF1<(^XC*$@XXMFaN!p-Hspi% zM3OzZDn!fhEAGIMBFC<^DW4gOH(tt7Nj2_d9>^!fd4l?LBt6{Y?s}9vt8dr`;nj{n z_&PjI#R|BKLm!UslGlX*SvZl(yQ zWuLUbw9&FIdO4I0e+391va}PKT1IN(1kedLg*p*$V6wbSPt=BvGKuEZS&iBHO$+M4 zkG?8y^k4AU*biyp{ZXftECbz`+} zZkDCV=j+vlb2(>goK1tX1e5Olv7mOP-dMT(8Rz)n@#&emcjxI^Wc+{I)ND;&zeLY9 zP`iwX4EWc*05wH@(Xy!@+Vx8y!@Y7s0`6KL%r%B%p|^20h!K~JN^omKFGPQXSD?!a zxBzUccO|7nXC;|_!V-Y%#>Nc(p%*xPH#7%B(E&f9+f%af&@RQfmmUL$9)Yj}NQl0a9$>%1 z!GbV!i!Q7FPcE6N0^WApZD}mVa!Q|k;^j=dH~7n6{&IcoYhO!GCpXCt2D6HZ}fWV4i87ma7jY@`lU^+!9-v(%- zlbTUV=hcD(|82Bv;v9E&e?odi8iHt9UJ5CjO?&27pyo?D|LD==cmxzsr>#R98f!-_ z&>iKJ7U8#&n_Ie7zRc55(uSxbA20#|>C$QRyb29h>J+1a6Kv!fM|&Y;X#7NRz?s0& z(HTwgspF$g=?i|~(-F4cb$mLZZl0S16ceyY?ASETK`@KAp^3>3WDNnjVKonfFHL=l z3*83S^hEwvY+j8HIZ9NPd4fk8HWD%{ka0>L;@p*g<7ERNzi{w;u7wEbB3l9tKH|i| z|As#4ShUnsrE!8v8c=XTXR{$qT>~G?K0i8FqiOl>TQ0N5!J(-rEU=`-OIh-vjBIGu zIn;9n8TvSpr8j1W_wzW_i_9gAv-H{qq9bQ(R9~nAo^+$oz}@8MPG@mXV+GU%26~)i z5!c3rK|d=A-%MRy;|w|GrT0wVQrB3@n?OKJ(GeZfC_PB=jZopO6H>~)5Bm(GlF#q%q@km&O-dw26GBuG~deR3w0bz%U>q8x%;d3rhw(ret2GMebBC zjb=1Y8Xxa_4#A=G95rN}=C|RD_XnoT61IP+rFdiynR$WJXToVWz=*vlbvO-g)LGy9 z*?5X1>A>Bj;oo(2x$gY@f2gH@_&?Xd{d?*SCm&~M&gBJ;$~dpsv{RUS@S(W9x}1e& zFiJH!PA0OF7hs5}$Ixl(Qy;eK`k9d12&%4Ly$W(NqOVVU;uH0;kA18zT)0s7HtbE< zfOxGkBr?Hq#MfBXH^oj!FQ3~0CGzLHM@>BW4^ zDt|Z$S6w;p5TU}5-qa^vsEfuQrKf>WDP=T~K)R8@?T8k}qcFu}rY*AhtWP@gwnHOu z9ThcLXB!S>bqo<8Wu2Evjogz9(0nLVO3{s9XX8*fWhR`9&dOV|^4C$o2pjnNSZSXO z6w2pDJSOp{z}0U*tx=3rl76+O%GeExfU`ziMEu18wcik+fGCs%70JI&P7=nYBZ`xX zv$v78LF%E0#+{n&Nf}kK0w&}FH%pNLWflIDW^t4uF%XqJDLzrrh1*8tWjE;c2I%#0RKVVv)vNof= z6g8Y5gHi|?qrqK&*Ml|j;?s4G9^2ZUTiNGvJy6KpG{^`1LB0!{dxv{RuuC^k@&X2) z%Q3D5h^N!l_I%Y}2VMoCnZICY%JDEC8$6rrgJMyfbE z9lK7w_kCc%2u>#1I4Hs>2RU0&A~9geSd;>RKGG-iKDaE;)qZNG{4rW|<13bwQ=b+;s1j|ZXKQ0 zU^+0PQ={5OreE1g;*}{sQ`NnmXsd{MWT(|9m}n-+gsE zj`JuuE~_gVatvD@v{RFn&cWG;IjrzIUJKfWs92Ho)8xe5b~hNZmvK9Maj zLZ>=LeQAiuPP0Q}NKGi?-g%D5!D&Br>cyJhzNhxjjuYd%=VoT#OS}I zYs3H@m{!-aQ~u(|HfQK`F~U||KeYi7340FDKKpEX84gJ~iGIs1x745f>7Uer{RiqZ zpZQFE{No=_19Kn7`KeERs$PBd)q3xH-^-iRh$0Rju0Q_cKdu*Fe6c?I(T@gy;VN{8 zUxdHRaR=(2KO9j)(ArQ3)wFNkNQ6z;6JgQ5h ztTrNo2{85;W^*q0=R?!qVwwOj)J2 zv4L0{B*6&Z=s5R*Wf_8D^##N&_IC;VHTL5@#+A4eks z=&oDqw>pn|cT70Az=su#_-=OdeiJAB-Tka;;)O|n_u7@ZV}`MQhK@#aFsJwhQ*=X| z1vh{JLtLmp4ieQxF(FT6<1mIg1n%*e?WAl-ZVc^+CsAQfY>Fdh#!y3JtRt`qbi*Ha zG~_zy4409vQ(ak~tee;q^3ugCb!6wRyfei8O-jO*ed9#Ls>51z89a3G;GCWw*s1@G zyYx^8I-ozlzKGN2)aDuJpBi6gB6h8QbnIiaCZhZEcVm2iF03*Y^w&nv{Q*H^M^fmS*3?8Y; z%d_=19o~jld?R1ml!RMtpDShk+N{i=JRi0VsUk9#dK0ZjINVBC^4l0AL(M4l>qqo5 z#nO6CIcC@8#Pn^vzILrXM3CtGk-@EUPWuB48V;tDJ;^FQE!zC zSGV4JYtkhC3EsZ8bJxx^JVTg1^qIpK|DFcs%GGnswH#$d&?5BfU{D=}0c+!^Y{6w5 z3PG`f;9!T^rxGJDM@4)AjB1yN79kCXv``}pMc8s%y^es9isH_(5jqgQM;YUMd7x2{ zKl>;$=}lRsBY?nANrj-$__RUu12{%)a@A}agSe|uJ$&Lb8=WQ6aZWLefFXZ7;5bg~u6oQJT};E4`BAhShj!=T_PFQeq}uCF$T8x2`tO1BXam})p8EIy{7faVQ9 zZ48h{p5(sek$lpvF$qHBBB%UN7P@z?wp9q^+P_PEz)*`znc9*eh~`BEL5eR~~0^5%N*(%D+wcBzi+K8mAv z*RzF{k^I&c!3n`a1+e4+yj9TZvWgg7t&d?GzZ>AmH+Z0}sz0vqi|s@44pwSCf9^O# zeY@+RNgm~sTWzD~VtkGl0FBIT4)7Odf)~CgFtel}(7WKq{F1|_ zdG8Q(8PG2Ho}TmP>h~Xcx~5kDyngS0|9@-$fqgBD6sG(y;QFZzh;V<>;s309ep(*W zzz##rVLjr5gdYWje6TS2CQK!M4vf! zX}fOqNmG-iLty2C=B5VTH$%o&`GUz zD`ylSuQSz|l7|$YHxAvEmYyU-ibkT+0#~PIPr_?xkSM<0@a`!!M|I>nAk>3m(CPA0 z)8t9$qlbZO@tx~NWYg)Q+k=rm&A;q>;FbYLu)|Y@Ecgon(%-60BLQxvMR}!CR=^|a zq1mQxou%Ko&0SJ&h0Ze^1 z!s?AQ0$<)tFsv~N4NspvUw0okjDz45UmWQ5MUI+TK3=obb9MCQ+BrLI(^c1KOeg4t z*3aR{w0wdr1;82ha1CEOBKCX`+%P?%Dgfad$AQT>{=Q(v}8K1awgmWgIbxM{+(YEo< z<3v*Sc0|u7;2q^MAq||V{HerIWDOPQQ81NBCoC>Fd2kJ#IwJX({61-;7-JRB6w#GK zG2$3F7_;D=Yua#0s_#{C7ZXlU^4o#()iRV!Lzo2(;tUKvvJTzoR8+--L>-6S_#QVF z9!m)M$-EENZr1f>#-M4`5^0j6Ojm8Z16mw0%H-|l%gE0o^D`eLtpM3%Xs74DG-jJn zX_YTr1D+{Y+lb>B>b}vP^_xfTsb9VMXiYIhaGC%`SLO26;f7FqSn2f|WXNlEM}QHN z`~qj$!IgQ1&`$b*&-P>z`N3mq!BsCa@dJ_k_PZ|?A3Vuhu^L5|LD)^uQo5$w>kX4F>V_i^*jvaM1j}~ zcqmpz{c6|7vAUJx`hWknH`o02jk@yOXBi#X&W`UH8W?ak;ITJqV`nJ?1^?u`7&-=q zzQ&>RKN5^yS*LbMMO+32_BujK8V_hfHtMDNap~G--LwBt-95ivUwZm^hQsG-AG+#1 zqm8e$#}JXO3 zCt~DnhC>7C22iQU;*^mR)3k+Qne3$khcu+vf;^j?+*Z4P@V&bJ{8P2|U;i`S%6cy- z8?gN1FLC;rm&W8!L9dpPQ8K2}VrV~6@+b2QlYd&;@L7Mt2^gYM#>sCH^?w{E4^T$6gW76?}H5tGsqgDX)^6 zCrZIT!KvsDbtFx*&&&-}vTk8k{8^g2Tn zF2$jn{up&QG$7p0r9-w@bQzc$WFj3E84~(0Tmg=CQr>(x7000M-3d9znE+CSv&Sf} zhxV8k7FhuZ3tzmgUkqY!HV(+k6)pxDX$(a>e+CGRhrDn}E2H~$EBttn`JTn~DP99R zSr5^u-HMZKZej321Xv6$p>C-yPCy>ZCV`3%^@_BSo|m+Dv`RAka~LGM>%%1T{1|^H zye%$w(sM!P>bm8J4uAxn``x@&N)t!4JAz!C*xot2t8U?pvXfUg>*&(c^}yz-`q3S~ zT#w!Iao()BP;VM}xh}3x5Oo5V(ixsXZ&fI(S;Z;M6C}N~0LvO5q^}?rW`|hf* z?)~MuZJvD_4C$!{Az8TqWP*lvl6SUGVt?bTVw*%(Gwj(KgEvn4>#RKf#MNXI&JiLz z>5Fd2BYwmqJX4r3n;0~_9^v#~PaZzJ=eD|Iexv@ySH4vj=ph~2v7KO@bK96;7k-2Q zKe9>=J5`sQ9|D!&n_33C8@pFl;AGFqdgZw9>xnJ#eJF?UeIO%)w^RjP_GDdZpWupm zqHl5Rt<%2yJXk3ru7-!a?RBUu`U>FWOgdqKkN%_m8~%Wb(r854z-`DM%SJ0<&>*-> z56WOh)=@C4{i3i9!;y=?3!UIVMnevR8UtNrzD%!ag9QQSFJ0%=06Y`nEx6h$wgB+Y z2T}oc6QQG%5w`r?@TUs&Z5kKTMm*bI10SvF?YyEE!^jceWb;V$I3JWs1vY9AJQWu} zx_*^zjm70IeT#J=#8y@nf%<^!5g00MmSR#SJ)X?TkSC21PE0|m+$t-daY8B$-&INt zDwar<;;uqSOpMz!6&8mkeLZ}k66jdOQT69yLa&GJRAvkNNJDK!F~3Hs!wKCgJ5Lj# z<3tldS_oQ)4T2!-DRh3HwV_+io7jdmOa&v_traS*|&6*&w>aYHlC z-bT0iFRt8^cf^VB)GdwD5Y3D?AnWmILh1b7>AIaU*sfxZ+~06g>Icv9g5b|BMZ`4Gwq*xSIL=Tc%tNL{^mTe{jz(o$U1Spc z&eK1vJ^T08ab7_5hGSpkb;R(P@(fIoUzxeA#Ph8fzNB|KInMh~I1P7c?m!(~INp(--r5uiWii1F%oM){8`#OlB>3N*LvRG$u`qvohGKgBI z=je&k*Ev@F+LCmRQ^y2*SlBp7gVRyCd{LVIDI5b6aYSRG<<-j<>yG_*)o(p`dp+{_ zH|p!`!k%NE)w1?fU~%)0|Jj?7Ko;I+zKPF)zROI1U?h0Kh3gB{V^0JdHAu@PkKm3v zhc3u_?cB?FpnR<>*R%GeDxQi2#_i|b$TOiz5pn`0;8P~<}n9a*{1!NT6 z4FZYW$|W0?R4abwixYLsU1wuus13Lzv(k)2P1ljyK+rf*E@R{>nitMKlb%+mB9OZ|Mw_6#lYci6`vzRU^H_+&#Np;$ZDR*a6y#mO=F#w87zbbyD7?c{74vk=6sj~a{6 z92i8EM)_;tG7c<@D+3KOkvAoxt}zr1hPpIVa13lG$oc8z%8gq%4sVRmdT8ZFt%2Em z8szm6`7+)e)l_oUuLBka_wv|*8JC?(wN5?;PjwLJ&~N$&Q14#n7#DgUM*-OeUf1LwPY8YUMwNdakdZZe*SK|)P(0?ft z$G*59=&Abe5TksPm(J78aaPsKXD`$rFEa=N`SzGai#Ajh8;efCio^f%Ld}XdTft z!HJl(ktdFxGU?G{Gd<^{k02u@{Ox%T^FvE|PSQjpCR*@bYL&lWWoQr8a}Ne7OZLD6 zR_low(JB22t-bF|YFtg;sB^&HhYiooZ`L=z@{Rg${_a1lLxG;e=Dh0ln7`Xnb;*EDEs{b%AYG0t03A#w;gkS^7P5++UIL7avmfN~OsE(< z3J{^;09H`=F`~7eh_kMC7O6*vrl4`|Jv?KrJ^>g0EBs&@Wl*)c{)*!uCkmeR`$R_O zMstBB&R&+bp#N0}yYLJR@Uk8(bx=+vC65gk4#LK_SCEihT7?BoiAdl@A|8z;^;w7b zP>R5Q3DmozSAPtRaER$azQHw~6Bu`?HzK0sa z?1tT9JhO3D#{MWzesWQS%+Zg`L)ja&afJ!C6FPbMEss`Lm_TJxCC*6r!lE&E^KUmjUoppu90*mesqRy8VuGO|Z+v|^)c%|v*KUv$3+*Y%1 zf4Dxl!bz`J*V!R{9&iR(l4zT*gU88iamxJb`_Y;p&i zGCRU|&alQf`yZUhTHB|PBKB3(zZXaErpl{J@Cluqp*3n=Dm@)0dTLMt@5#Ws)yL|> z>H_CLjn;v=BjCfk=Oa=hbYws_MeO@iEbU^2=;46RZVJwFewb%)MKRR0^I2Hxs-hg}OY6SKQ=-Lu&RrxuX~9Y2(;S zf;sJcqz+;~N_i1IxUyuUCZJ#Z^%^%I3LdkzRfc1>9zc&-;$A!y}zAU@oS5Z08U zMERo{vIYKAyTV?B2^!|qPeU=nj%2CI1feT;7>|3vSKA-&Ill9`Se7IJ4=g8qFpRbv__Os>=r#{UvmCh&8 z_YAl=WJ?1@!|mwMDsUIC!zJozD9&tCHsuV;<6%Qy8BeTkd^I_5$SB~m#F&&AkdwlEajyOth6(0K>m6L zQ;iVpO&KU;nAB9NO;C~Q4UnYBG!rOk9mwm3oq$AiMcMNlE}2V|$VXk)0dF0m^R$Zg zcMS`KyMD4-fDuQ^*m#I1IxTFz<#>PZ0L!ckDNU35vsIUs<@%k zM#-V3G%FagZ|NK2Ajm;&z!g^WOHXT$!s=X9kPUV;V!eJ{mhg;W2YK6FYjc7JAAYw7 zXaw0qZ2?< zY3l!s;%#u{r45cWb-6Z3-~1>)6G%{xRYpeCF^c1H2*G8l%Eluw)OC*oG{w-mPoNtl0K8iM>u9eSqV!R=z|h+Q9xu#ayuvaDJP`X-V~G0 zr%u)%;PB@zyi}{#@2LNZp`S^X+Pa_#LQ{sy}I&d^Qt(lj8!6E_(ORsFkWqjFWp zPDBj;>SJUEKXs9#o4gQ=xxyU`T|9q1WhQT1?WmpYy5@v>Am;?mD|Y*I&VFOunXRFlRWKG^WgPO^lUI+xsUfv(T{ z!Yrmi@`_vXDyQ%t1~@O7c{o=ELW{h2-e-{^sjqOr;BOvyQ%%g?L~LTCt31@)=66uC zfXKl^8JY%Ig@IN%=RA#{0h~tJ@A4J>QCIT~{LrloT>-DlHJxfohLzM+@CzJDb*0kb zUF!rHL4n3F^zjUQ?44xK8F_?jet_^7$f#-SufD?7oTwL14N}2RJG7kor9YJC(BOg` zhi!dpVq+^}Qr>u3uZ>f&l60%Cfm&=8`2RfIc62HQ$_>LEn}k0QE|Xyh49LW}Od(yx z846GSn3C3B#i4wd4hv;;&t>RfU^>sEPZ&es_!&n`I?>V^M`Y>9M%kD)LU&#z$UB#f z3ZgN#U^rW4q_g8jXXWL2QBhpKs9^rICo`oL$a0cn0?OHQ8lK;8ZfT<)arC()igVJt zyfh{)@O8>6Z7Y}fSY02tA}p0mB|_=^2-{0w&^<$yE4GgsMS~XmDIk zq>5B@PZ^QF^-1Rn8hFB0Pf`#SoMRf6z~#&^^6TgJAN%Y2@YSd4(trC;>OBk{-M#QK zYa@6QWAq95AkchnSu6bn5zwY=vkV>jX!}mC!I7o*&ap2-A&Ck<9y>jFY z^^d16*ME1@czt`vkve{rvz6%WEwi7*7jgMsb^{gZ6kqF&)5YQHz0#=CyJ_2_PHGar ztlpEK)NMm#Kqk`x0d6$Pq}ous2g$b_dQ`}cY2NKY^YYXyXA#~6uWp4tcpz!56S>3g z)MINBzP*f%+A%X%58ZvJCTF)JV*?w94@J>>Ehp2FC3P8Gn${x-+6%HCUZ#_E%@c;< zkF+faL2I zT(5Z#M5ah;CW>VO+M#cdevM0JWw$B3>2ZG57=e+&_k3h0wF-{`!BxsQhDkZo5KbnK zQN$6Vb9Xpr3^h4yV64PqwD{EHrBW$S`XW>)^A(b8BCo$G0$hX@8EM2gPfH0e(c%Et z;TUu|H@LgV2txM0{$@W+@!7q|Kgl}AK^rS+;j8l3o;pHHhY zjeBvhK{kTSJdr%+PlJmhM(LoBp3)lW*fj&xE|<*M$bP#vCJN0vH+ zyKo{P449sa%TDjUw_drrtv>qHWA&rC-E|Kex(=~8Vs#MQSpQIV=|99tkwki&hyevU z>??Y8KYvb}hFRdLOF@dxn){M*v?ypucb0 zU?IS(b<0gRb7aRdZ!?sBT{$3u5t%`!wXQK2w~37`5o{dXbw{1Qx>SdEp*I8`t)pb3 z?l^OeQlzwRaa1tK#NP0z^J-LJ!z$dYz5>tmctx74>4F|?DK;#)(2S>H+3{vxs%1lq z3~V@4h0v@O4mg1b4yQb zpbW*ihiQ)E!aNzK_=dwUa!h7xJo=bSHaU5gvI^0mqpnMZaQ*nUoTX&Tjk)klBhw&8 zeD5$uq0(w>LW^t=s*PVO1o^}x#!1;sQX)9R#bF+!REty2UE{$yjR9%ksw?m}5eFH2 z#Z=RmM@H;<@S2Va?`ulC*`d)AJAfb%f$+ zIlIN+su852;kk|>jYJ#i40Z%WK)yx!9DRrt0dj!Y2#hE;Ku9KSQ(;JvYHDbbG9^yk z?5V2fp{lEEu6pxO`ThRu-s+BI5iOt!ecrqGp0m&1Yp;2)J)C_qwCJhVsl{(AHY%ki zX}1pRva#XMilkfn=p~+H(h2YU$$QS7Lq^yooy`v72eBF7aL0INCEe3bd-5FE`sDdh`?)c=S!O&$F>ZJmfVAEbiQuiGgqh2o~k8!wZ3ID^Hac! zeWjGJ*(;Bpipz>Gu*z%q!g}4iYj<5)S;}&78{KZaY!pNm22sWyQ20c>+E?!mApZ>1 z#GU%0vd|5&(LHi%4`>Bx^1bzgQf<@NorJmleuRQ9QY1lw>}pvVY+9$VV7Pkie62zR zuM|ja(q^yK#za}n9tIm7fiTF!YN&6NN=rh2MKT{B9 z3{1{Vzd=V-fxW>^QKPn4t(tp`V!_lHMe zzy+bHdq_1y}q%j>n~YXj3*a1SNur<--6 z;B7sjmwKH9ALxud8oT)}<%8-(Pj5OTho?kIzKEQ7am`nvS8t{fk&ogcuc$|-8ij8R z(>}?^I8^e{AjlPIxHir!pC}tJ{>B0NYd#FbM#u=8dyNaXtXn>bA3w@UnfZ*5K9`Js z#mQ#wOZ3E9n1PSX*ep=aZ~{E>_MVP%NV7d9F7qWl=IQH{)6wXlf`>Z6wfF>YU~ubq3^>nSh6m4 zH%>4OoGCb7AJ$9HNS%pNkseNj44cJv?2^J@5&G@2Wl0xWo`K;a!x@y-E`mFplqkWqL_YWT#07wS{5%{wfBCGyAikvFr5NGhe)jW9-O{H_kYQRIt&D{ryk#*#808{EnG5aR z>+)3x1Ady$VVPiLJ;727L2p1<66B31vM)n#X&kg!u(n=@rb3rGLRtM!Y!0yewl8D> z1u!hXPLD047!EgMTX_^hJsq%9oNmO?7I7mWzfG7LOPq-Yq7Yz69IV|lQnEq@M#xMV zFYD!fA{X@mfkgl~BggP%Zj{AL48J5&NqJ&fq5{R31BGVo74S*R@P`qc!UA>_$MRsH zajC4}sjy>I3er>VXG?Bwm|JNze48%1vl=T-CIKe~rz%;>BWguA-i9w%XjWhVp(iofBo$QlfpH%d5Vla_Urk?jnlBs@{+jZ~UG8&F2W!CiGVO`KFv zF;IaW8Y6kAeZj5qOXM9v7=hZzftjVbxg;oY+7C!f}>zh z@LLE8Ae+dI^xzbZbQyOv-ozOW-)#~o@IlR0zB+VyI0N0jGrZPU8Mfk$7@0TDFp~1${;YsIF`h76gFU|h(8#z#xRC`LI7EK2ScTaYn z%iv;zer*ZCUmoL`Z$V)Ywmn^k@ zSZgY=qS%GCagLq2Wjbg8r|C;TKmeqzM8~~n8G{(ox@V_T*2e&upL!#RWPz4-DaaV z6+$8KFSnH-|4t^_UCwIpaLhfR6agCrk++q_9*09wDxl-6kWRULbf5@224rso=KDTx zkKQwi6Uu=OhX7R`;n^UOqSi;SI0E44%zZVfPuzOw2!j^q(&8jn96IH*j-4{0h5H(* zdmBy7?BSz$tW;I7yD$A3s|rhzKn)|{hYHWRUN4c0-FA&an79ZZ%jSqPhhCR&#&HZ` z@#~V>9tSpK8&Dc<8!Tlc_yIoRBt#9)9*+%?k(q3WLrH_)jt8!rlYdJdX$APU($hQ)lzz`Kxd&q8V?w}E zoi-Mok-qF;>9^(NM;s>SX&|XHjx@YzXKSw^;1c{;S=Vc4wDm}N!h7mX&^3SsM9y@I zV%-OA0j0N)Y1L1xU8#Td`q>Dh`T+m{KmbWZK~(y`NO|P^#riM~e-c_V44LfW22T_< zuwFcWg{@C+%G2?iJkJs}(kCcCFSKJ|i(czAmsTwOd~8g~*!TF>*{9Ksj+>3TLC_)7 zo|hctp>A>LIZ5Hc%QWgX=v0;-nwDwfXPKr}O>&V%_$mPF=2q@=xkW?1$?#$tTHv^- z!Vw9%3)5N-$?{PtavxOT(T%!^USNLD3M54=!oq+316zF{eAnd^TqfEC^c5Af!m?wWa4xJ5&D2GE`FqFm7dFL(?mmz*42*=wUIjLxK5@G3xx-MA4O0RM1LC9pc4R z(m*`rvz}f>G#vT;<)`+$XbN;hE?D>Ac2ZtV2wK zmu_{*I^ulMwb3zqnf`->&Kdl4SkYl***!qZl1JQv7lzNZ!%zu6;BoZBO86uS4Ngt8 z*RF>0^`|N5Q`F$r!)#{cekwqBqIbC>m27~p2c~QcAowA{-X&SVGcp_Ky$&?n+fzvz zUmYmCR3|g0zuwgL58n?!i_$ z8ysA%9s3t~9%KhE+v1^4Na(pn>M#(*-V!`fSyqnO_%;#L2HlanHA|>J+N78v4^e_o zmQ#h*UKdg1xhm-zG*D{1vW}mS*W^|HB=j%-%fL6!HpM4tKPp`+1fKAFbj5rl%yao3 zp}g8!7{C-Idx}26-*ygq>XQ0G&S3)$P;SUid;~~4)UV!X7r`nz29C;E+vGYfBn7Yk zmsGyPM++pxW*}cBQdoS%EXLNtYU7jHxq~SDRd_14j8qt=K~_tdU#?N~o^*uGD3pSZ zkYbQ98VEKDJz=X7IoW93j5PtrGaW!&D&Q0}81a#Pe!xkASvTGHHWshFZCV?U>Xt;U z*1RtNm9*eXqvcY>+@db(HMixnHl5m2nsPB-=#BHGzy{VHLk(PW*%JUe$3ndn2kOX1 zZ5uhNP(T`4BlU zJu+-y(ePH8c5fs4qRh!T%G1FHC*qL@l?hpc zxwrx&!;?7N1Ti{V7dY^py|JtSM`Yuf#x;7>x@1}$pe;HOU85hzL>?zroj`R3Vd&vW zYnOX_B=!ipMwO%?Yc<3O)BWAl(#T7g^4NOhgE|qe%5}Abyr%K-YB-D|zT?avWxF2A zNA6Y(ob-}mfNZ=bOL-?B)%yfy)}wU9cYq%|+;vLfriJ@2jm3H+Yq zvB%dIcF`Ggm``a*ldynB9UH8;TR`{5w-#8D#bc;!r!oevX@DbVYSW1#3UlWBt2v{A0B&7pO>2F@B z8%~OE@{GV$7Efe#6*)739(lFTimwa94M^ap^1}u);*xXqN|-i|;AYUP4)WJSUi6l+ z(d=HWex{7dBEHPIDTEtz+(6K8595wFT3AY<$01=t9Haso82vSH8jDf5p;8;A4#NF> zydJjN5wOY#1clp?IrVr!y3ExM z%o-=rpXH3kJW%4S0^@oZg{j=Z9jI|kM&Fdz*<}6#Wim(8KpvH606&g4cxlk!lRnj~3LGsFfY8HD2daF@PAJmPRL7$4S?2Az?U;ZZ)%<9IA5|HMY8#E(h-Kv!=+ zE$!@^l_9^OOeirhxPIUcL$6zgK5Zzg{E%(S!B! z?YGsAmtU>@EG*Dpjk9Y&_+}mQ8JuEV;)o+(XQ^z<9hlOtv&3j~ZB9T+ zXK06s9eHw1zVzVe4Yjjqc_|9{7mDN$@G}JE4FiY}ssQ9~J0W?e(f7Q8jlYd2IDwx| zKy8a_<2p}!+gS6_xky#ES{JU+uGAyEhg5PaMKkQ9atP*{ zjluumV{aUI+Q6WJvt0Doo)J*Gu0G%1fEt{4H@MwU`%t5fbBLpXP}4y^c%Br3GiB7M%*rN3BgiD zey0l3t5*)eup6*AT5XGe8zqY+Fi;m`PtpNSJcN3H-G{f=$JpCzi5FiL&#K5r<=QYE zEmC&PNYCMEbY_T(bjvOMgf>qO%Ocw`=5F$=5cU^+uzDp9qgR$z(AO)$E5V3h5)2J^ zm}|U`!I?vG!1tlc;9`y7seT*au0WvjJA93CV>HxlcYT!c z_6UUAnw)99xt_+(ke13}B*R~#2J`WxweKX3lLy{VSeXRXv7_L6yKZ0_t_Z~xz0XHw zH1hL^L-j;YX=EE(Xq3Qvi*38fntT4}c1Etci3Q4qQuV`L zsEnZ+;c8?8M)}00c_*L93ygjY2K?lyxQlZPABcW^V`H1ZnY; zAB~rWp!^c48_|g~QRZE@&eVT8@n<#88!pG@=j!oa`_Jo{7hY#h1UhkA?L(|c$A#l@ z%sKL5YjYe=w!5be7ZF(Z(12TZnlE$!M4WxjsCz-=jl+FYjv}oS+}K)02fVU@0Agec z;;P6tKkrt+X(pZx9~ z8}&$^Q7f=}SWx@1NACQZl^7JHTFL{_32yVM+tkrc(kJ>o7f-B>r^k)zqo>HjyhiX% zEX5;Bc?$0c*~K^qBnu)XGNv(e!pb_wqODwa5OC`p(+R^(`7G-?)?BCAEj|v3KjDGWe8+kn<^! zv{yHQ5>KP&J;AO{Dn*|a8LNG)^t!aVR=@GbPu5DEtUr0~LT$2r{geS%!gqL@{5IUm z)c~dWkzR#$ijVTmsd7_Ba>X?9QN9KpoRieURP30%fWA)oaH;87mkl*}Gm-+IVjmR9 zd{S_cUIBndWC$MAD_k4$EjOIim%in%krJ2df5?S*_JGk5CJfQ=;y7i#P7PrgiDZ&X zhbPYl5qB#wDx}a9ln&lT#xgcAh9xjKJr5OK2TeYmSu&-N<#7HAJqF{dtfA9RQ>2Ao zQyv8;ww8B{GfLt)Nm(s6P-gY1yR$k9g=rU9=h!$6Bb1cKY3mHv-N?_#GH5~*-EL7- zE%XGA^Rfv9Fi3;8C?b&KNYeN?T;Mf%bwVjl-Qf-SEiSz98<0A!;o9Hip+H(zRZj&r zvj&L@XR?lKaR|Wv&74Z2EhglVk8WAHG;g4FSgAv5fENMp58-PhIxzR&a?T+y{UX;o zlZ;1?{Ee!Q{G*zA>`&^I$A3^S{ty3qP0s7gG$86vK+z7va&9iyRY<68)bu!qcUe|T zIsY_bkCv*-FR44dCuIulX_>@SDRV%x4UEqvj1sB3aH5~Gwnu3$>WE>tY<1ds{y^gf zf(|Ct3NJDo@QCL&)Dho3V z9dc2 zS?QAB$eA>qr1nk!6WZ8d5{KtjDx>r`Cb{oRn-*v0$+OO~R9BHr^17=l^|6cJtKZ}` zy#HeV-{3j}>m1lt>>sZ4tEM>&ef+`<4=*nBKK9jmk@vCZg_Pn3 z&Fjcxj%{CNr>@rj^3AjLQ}@17w=LXSzxT+O>kO~jp5etz%2Tv;&f0=!&iSZa@?^w) zs}x9v8npv;OdXY9@YeEaF}PG-(MS2#{;dcK-I*c1Ht2DlHT6)Jf6;*fKS7#0kv~J< z2>`gCgS^&f<7wH(kBSK{GJgubH|jt;-q}O(caH-_(h(xiK@&~7(Iw-H2tc_QRrZ)N zIBRq$aeCIUtj9YC!e%m?r10CQO|xN;g)|jch4!9Gh6%y&tkQtVJwd2Y5Mp%298|1e zq7JR@@= z9`L>9FarQ%5GZ^UH=ToqY<}n$FI?uPASnWJaY^?hT>*=X46uEq|BApabBZ0 zeQ~Y+6SkoK)Wl=;nOo;;>bARxj)9Z<#ny%g-o^3CBhSH!GXsMO^l6+e$=7-IV1^AH z-B&7mcR9H)&WY3zg-%(yGEtv~iF`Ubb*Fd4b+wc<$W`P(gORzTLBo+AmLjB>xV0e* zSK9dlkL)t69st`FJPvWnG3T))XOkXII~B^u8PRmW3u#fNAxm0;VXwnyN8At0$X*?= zL2*X}CxO$O*Qr_BiE-IMU7IfBRYvAfZ>*bibukTr1OP9g6K~K=9@7G-{o7 zE`c95K;~$rBm)hEuk_XC0LZU8>j`p#mkufAxhweTs{`yMjE&7^IB8~*?M~43S5BX; z^F-9Qoc~cB-d?JeqYu>HSHI65@+W!sle|;bI(c~jZ26!}_?-an=Pymw?Mq*;yH>tb zx2=D<_N@L%y|6r9Gvslz$3;dQ?%X|7|KeL$>LYibsE;1~shZ#3QSbZU{WM@#7|L2~ zeSwzPfI%&Aqkf@@FX->>M3l7r%nfY71v}!TJU}xCaRAFQWUp=M6XS2fr^euVe_tc*dINsD@D#obxVF+L zPD`Ug9DK%^2sQe`6)^TK<1|pXrta{SRZpaCGKL#C>-goFjX=Y(9Qk!FBRp9RuHG~H3Vr_a32ym? z-$YkF?d;gU=3`?L!v}75hQ)Y>H24bEySRVv#&XTNX6( z4;vu^58>#P6oCy2_qKSo>o*^Ls=o1+uh(P8&+v-hG0amZNTN)_O!PvRVbP*k`B zyf^a%vb_|0iUu>(g&b_0qq|NZ11-2y&Uqo1Aj?zhkWb>2d4TX*`BNW>=CN$HPk|?$ zAKWCzbage3w(&ESLg;++I@t#Btu45v;Q&?|Rq9A%X@hCQi;l(K0zTlxTYbykUm_Yf z;7qCwbJsuM5qL*ysUy2)gR?@l93Ph)XA;;( zno^ta(Pi|!w!L;u@2DRf`*htty;T4GYhSA8kAApbn7+5Bx0bRcj6}pP6v>^v^Z`LR7zNdcgB__Q`mlYfQYak+_9Q@KScGGs+a+Zrdw`et8~ykFKed+|xJN<4Wb`kJ5yz%b5~gM5ay`NO#1fZnNH4$B?MqZ-K_xSIfvmo zM^`v?wAT)-$7wZ01z{d)#;f+2x$vD2;iJS*%s!$C!7I)&LqKm8l#Yc)$}T1mVmoDo zZG`Jkf}rxjX;ash3IeT(ILJ={@=+JRy++(}lp)P%)-G{^P@@-h0>_zOzJ!-XmvlDd z1_sK5=4$fNu*FJ{juZ#=1dxPxcR1jQg5*Wx=?V3#_}q-rb8h75a~H#FUq@@b{&k(o z%~~bY?!C@j+~tQ(7h3r1rIXy_*uX-41ES6?u<+zD1{M!&Z!g#7i!SY*sF%;3=hd!D z8FJ{T6CC9iWgTjgQh6fglT6A!^~%|LbU$?I*R#I!1$;6-4;~t7 z9G;+p7kVgw$bny-0nu;|Q-~Kht1EnTeytm+nY{d3zd8l1(SbwEK3vkxiZp5TK}7Gn zOxMFn^F-jPH~G>qE095ht#en-MzzW!y$$)Q{>XW7%$b>xnUQ%Gf^ca{bQ^r_9ksKS zU+S$50Qare;3WAC(v*cX7?g2^-L*W)3-7=dSgur)Zt637_T0>3l9srG1ab6baJ>jX z+?Bb!^E(Bgiz1S%?LePPtk-F*-4S|fdZw1I?yGkr|2cw)dq;NH+`=A)ypWxc0I>zk z@Ldl=d1_;@cckVnpQ_(~ZnS=M@6mcKi9stwT3IUGLfd zzIyEq=o!0I7YIaeV%wCN%ggocQ@lrh&my=&lf43U6xpep_7>z>w)F@3<_|mrFBlWv zMxQ{?UYq(ZwJkpz0QJs%B%&|Mkgk=S=UO_nd6J+*8hb9L>B}{~M@cwR?@CA|w?B5Qyzks^VM0 zE679~xxh57*K?H!zL%V6&nLw#MjB+tj1A6%uiY22QHUeyyZ=rbp{T<=ro<+ zG>;7{qyPn)qG@6c{y4BP$MogX03V&C6a6Z5BI4W=hL8rmIgA74Ue=`1Q5of5 z1R-tBPZhnLTCc>)&{^mP3^Gb1BV1rg*_P#0d{NWLY^0^x$yOV9ds1Xb9X&5$HXYT zDd|$*q9qsUPM|srAs+Ti=s2tEqc`|Ztqv(}% zcY2R3mQZ%+9&Iu*9`=WGxlh=#je<=C6ss%*ZjV5}&( zo8L`jM_$G5@LK;SdIiP=t3Z6Gga(nJ6AcbDU7ccDypK_&m5GK*n8WCXdVpb+l*oqr zBn0;$XMPBfH(5{5Rq%xeE(`AY3M+#rVV1QtpEG2a=u?6SKoQq$S))w}7)!{2^@V3j?fM4ZVi$sS2$(#@0o^^Ds0ZX}# zn*;q2PqB|vL=MuW?yhCq8R!va_^1Yj(IH5=`qDA)8GQ=ErYG3COk3Sl|CFh+wATUG z4WwM2nVz;dXcMM0H>k7Kv3qT!l=`q6rRDwb2BmTP5sx$$`38)7s%_|aq&>1G54xS6 zH*}?u<9ow*9bD5N9hdUrhOz}rh=KR=MmHAQkOvBeYxyZo4FfDF9KDLda5sTTH|T+8 zUEpmG?wQvwvLecn1>T+rACBI66Hld2!tW&@;_R6xd-dEhZRJ?a^A^uVUknE>i&Ja0 zYv0|qy7Q;%6SthMyQh}xe;&W9>e+L({=I)*fBoPrZ-_ipCwZl|E6Lb(3!Ds;uGVF` zL~|3n>%BV<*B?Iqb@t$L>waj>KVOO5v=tvZbna9+YOD#4VmAed!!z`_(SYg-cv-|3O_~VdLIMJ_{+gYxuPwdCxU-V_KxgjU?;|54eqCGbNwwP z-aOS1qwIXg!Nb%<{qhlE_BPQfo`N7zULqV7ryClDfWU|p6{?DA(M^meD?v!hPzB{1 zFBFA3RO$2xQbK2=W?pyLEDjLTIqdgmN+-Y*cJ0jzA@4{Kk2lL9mz3u-AuwJcX&u$K2h$w;hpPq z)L=WhWu4l9a3s)j+yhot2{LE3IKu{v4BZ*jtpI17z-w;e5W29G%^255&ed}lpRd=i zEY!&hn|05@y>%1sabKpVzMI`{Usyd{|9J9_`rM9*`j@AktViZ|)SR!*SyOyUHr)avM^ zj5EQnPchx0hJNKOiU@6SD%8}C5SmA!j@}uc#R+fo1njsyoj4A80v;UV)T?qB1XGEv zz{3bvROOGb64|zK>zn$?Xk=wo)-MmKf0HMPeW$!Q%NwItX_Kesuzrn<3l8}rE+L+K zHqy{;-HD3f8E|zX0*qrdFKOz4PrYZGa6qbOabP%r7_%iXYPid;w43$+T%E7kvnOlu zcm83${I~ztI)2y9b?&L>>b=MAu8mb*)hbMlR{g^ut$~Z63sZt?8dBuaSA2&)9j$fQ zWHJvV4{f-(iY|z*IA`w50>Tx(=h7)0eskDpt5?>*A)_NFbMs6D_-*oPV4aSKU`HcU zakiX|fKHck4#`ESWRA8ZOySEX0wd3G+M$+P%2S@E$IqGQDHo}6_SfOlo-YKhg4LPP z7$t{1lm3AoMnA!|d$vuBypWWBMYXU&Cwha-J(tJetCr+1&a!uz)dBCFKPcNmU-!az zj|fBE=3Svzuyb)wePrK3RES~Ds~31-mTwg#WN}dn`;;Brzg_>`#J+lHa-m*6{YIVs z?(f$#@4Kf)&;8rFfBE6My7xmsx4qa!KHtWv4JL;=XoT?Y6-=NW&Wro5< zAm+nnu;(5^;PyIV!RMyoLDs7VHQ_Ft@1Rf$IQ%3?&$zlR#s|i z%2$Qz9AHp$@LFxM4Zg`!e&2LPt-eupxaHVA(T*8~VGGRfIQ3sa&{s~b*3LZ>tgyk9 z?C!(iG#NTT6t&M4n$DJrbmG?>h2*Q9SvEo_=YQiSnrv@YXP|*=NK%M2e2hFK>+tdq`e`5^ReL&Z)FnW)KRxn6NI{^ z(KKC9OgYEKQ#N&zCjK-QSq_d&n6&^G%5nY>Pd6XD<+gh5v;P>wTB(J_{dM7{Q+0ti z^c`SMm_yD&wW4js1a?nC_mKENF*aP!BPUO!!j58cW%By^*f4rONh_1ME` zEz_`G;UPd_kB=w{0dSgZf;-`0+g|5WWcaFD=f zEp{TIO|zK52l%zb^G`?i?W4V0BF`-CDY7xJi%#Q*^_1Pq*N)#MIPeY}nu|wl7D*S67`*5|+ZbY0l^ zX8qD9KVH)-@8;Q+%QUXk!;|gOGQqIcxLe3X38d@#A8O-YIEK#tkw+e>U;p)AuV4P< zU#^dR>|?B!*{H95{cCA3o__l2x{arVf8iH?p?>3k`;9ty^Fa_DsZV_36SZg0o_h4r zN9$YP;+av5{WG8WOwxvM{q@K%WwOGGp_DPBDoEEaz^f?QRVPxToEkRF<)*7MCTN&e z&%IL1C!Vi`T^~SEDL};C5w-v@5}~LG){!e44+Ydwf>EC?G}$a-SDu!l>$aI8;)teG zn!1JO*m4&D73vdqTdAn4gxz2m<2h!ziP6q62~2!W3Z^#B5R@o@pAoi8WnH@+yeMzo z6GWQLBeA9q^67}f8|Pm<=*q{TQ8F|n`V}*10CVSMs0CiB@HWQ&34uSZT|g!t8QLI^ zHMcWzSZPR;#-P!F2Xu%{9IJR{dljqDNy%5L(YUB{;J-pshDE?9y#)B130!$joiQLl zS|GAjW?n;U8hM1;iwDHo1`-T(pjwi_K`cu0Bt*W4`*D`oBQ)|#RP^dH4b#3|?;#&h zcu)8tCe(lDhtJi2{J`PbO(b`Y;g~Ulg1`Tt_gu3|UX*3{&5%}-A-97Q0?sDcYA z@_-}d!T{v~o~|u0klQL=I7Luvozm;vq5&uFmn5S0*|~b(-~F#^ZRxcdpV>#9yD}te z??Yd(ong5k1YKj)yIDKuXX?bIje76^Ps=mk0BeF@^ffltxUZ~SssjfO0AsV>ID4V? z?z^dOJ9MzlzHzc%JbAKq@7`T^+;V4~Kl4Vtbm|oxJAQz`K&%~6Fi@hE!S-nK*5sA1 zEFPoLSz~J#8Uf`%ZhI;6Y;tnB{{F)UYkKlOtF=oz>-E)(n2omUwnBOn{TL0qK1R9R zphFq{g(HTF5FUQ`;WQp+&YVde8-t(unV+ej{n?+bTW-0fjvP6X^wV#gu8)8Gt`rT)F z84U`m0ii5L_xU5QD~IeQq)H7|2W+(nJ<+B#V2mBDix)SIT6AhUtvC$J3&D8_9lpjO zf92qy(bF+QqhzY6_R_M1*m4%7no)O=vvS}>PNWN=Z&HSG9UV#s>Ch2JC62bOMF`stb3tu+l@nrGSZ8%zjqqEr3C zDTxOmY2?#=@Rosmh=YLF(yT*?oD?MTvyu!4S~vP)5CON$1SoAo9qI-*HmBYSgZ%b( z0?#yb8$kKsIwOxLk!fI%o{Dsu55lc$9GmvYLzLW;c6mIivA9a_1d&_Ust~>)UEC%YyH|{jNH9?sV-tc(C^F;LVh*rYg2UTA+b) z`;lq*Hj6GghkEnD19gbr(cIh);2eQ2=)sl@l9a8eZg9T5w8+~XM|f-FS^Nlohnqf= zqdkr@gw_v-YEPuypyMxvtnwQ%O;5W+XJ;EW01iz({`lkd;DZkat)oYe*6;rA@7AyW z>aW(NOP3gXyO;*Xn|IxHSDin9KKX>>kl0th`qldS*S}tW`lo-2^G7H$2Bzdyr_?(h zXQAw!v>q@<`64uR*Y24HJi)G>3%mBCdpap`gnpqMM6?cy_qcY$ z)zM`552MQ50(`Pzl5af+01AO?*+CPaAqjf`BIUy?bpvAtzsL@{q*^5(Ha@+gjj~e5 z@SO(fOPJ)1{EkDBUe39TF*&4W0|*$6AeAZ`a6uN%*KBS;gZY(laFYo@^qf-&=T}|< zE0KU;yq2W?br|sin-rTwx=>^DAc=+h&W6fkQKb z?w1N#N@vawu=D;Ee)G^Ocnup=(Fh|%j=aGDe9~YiLH@u~^hn!Ee&wqUi8J^Z$Qjjp zScS}!|ACbRB7Bqi#QAdOc?WFZIA}D~x7ZN60fyuNx#*EZsP7&?6}EN}0eUSw^7C&w z8@n-h?Bsm1z`Gt=HjCwyx`YE29nmkOw=Nt$$-s{daQAYV$yj0T>*{iP(c0c{3Am4$ zx>Q>I++-5o9?vA+``GN>dhOCVe3kkrzeU$GPPj0zUR_?Ulc!E0ZdOAviNC(KQcphh zRGmG4p$;B66uXzWYcW*uS@KK6FPN zx$E}YwQFx3zT-}YrLY}kEp7~{%BO91?HC^ zZi8$D;vC5PqaXdK?z`{4K@Z|;efG1TtTR6>!?)%9)y894gv__`nmrOpH0HwV16!v2BN>eV4vYKy*#fCEtm<^h|APk{G zbUJ*<^DHquosvq=ANk~C)28zH*X-@#DX9gvil`yNuMQ3@D+F`5`sg@OYEt~|`)4P)QTw1Opdk9Jq2t{6r_KjhaX<)d_V9#MDthsEKBU`N0Ja)B;iK z_n&``=pDVk!sBE(m$ZLo6lZ_PZ|_|B*$Z>^C#ifSnlzmSybjGWm1|e=bl~K$j&O7^ z&BH|JTzP-nofD9qK~azsH&SGDYmj_FUOMcQZ=B4ej!IF2EvrpgaUrWZt8DlTwXqxf zfg?6c1L2#zG^D~;w=C~a_1qNiQ(vLMgCP^rAdTv7aDky5+LZiFoJ+X`R!J8g@{wPI zM$3A#Y+TvM3UT8%qX^V3|D<7po`;PhJ95!?awv%5G3b#JDkq-=DtF+c7pG32>>emL zwI4?sY>b54y-gZ6hOL6E^bP)8_+VapujbX?Jahc%I`zhx^jMyF<{5A?$W~LV+mZ&! zL%PL7xl&eg-N^3g=oE4jxtF zE{sD7(oMIl+JsTW@D(!AYPO1yk$!UMJXIDW;Z~TAg1p&zG7N&#uM9A+OGsa}{=ll^ky@U1D_zX`m9=k+Nst&7Zxk4B412iV?P&01a{6P8!M) zjYcpk8Ncu{jV*&RF(~qic^oBpI@#Ax7Xt@=sYup0TmTT>$tQ4i(m%s6;$WTdMx}HI zVrdyRNFtmj?ggtGIvRT_Y3LJphze=qM_pMx z!=i`i3;EO&cS2T6jI1}=7JZB%tRu`FZZU>G$C!5yrRXS>YDd?R8N7!Ga98Izw_yXG zb%nh^<$`nautL!~T9331vPa|lT&?oHPsq33wXRz}ER=3(28XLWk0qb&IYoqS>%zG_ zjWZ?D^gO^Qjyco=?{mRT?KC)t2J&;iNA1E zm#mgg8RZ0g>r+3|;{%p2hSPzAMgCF`4QBF@UzNAvu?L!E?vzOnU0ik4z}jT{pLiq{ zLOcgJtG+gz%3^}$zRr)iI`Hz+#hRU+t(y+)tKBDhkBO!X+J56c-_q5q@GgG(BXAVVgw-lcO;(D^p9kl+T`%o(ap;F{lTDxYv)0 z+N(%_;d*b>Xo9(dVf3LQITQm_?SuRXE1s<@(7=2VOz`KPI7!f09JQTlos2YygIJQ+ zp(W~(u58UB(GcdnP?Atr`2L&DJdzjOOoO6AGZZI*iAsZt7>c0Er2870Q-CC07~^pU zo{__0qBt*pb&B91Z_{|vcsO+B!<9azZCV_wC1o}F$jnKI;u_<|k>*vfq=|KIVGP2} z+(~#Lg?7DH;3GVt%vC0R6+GXKyA2ocfg~sMN&^ z%E|Tj7dDq_ady7md+1`leqk*Wwo42jS<(iz8))PqZ-Fz$&Z|L0@ZuA`fMZzOiO!w* z*M-0+fsTBKE*o|A9KL219dmI~u*Q(uDwQry@N%VPSI^1oH|3-l+{(pDacAL;JjwPv zp;;P5!ZJaOy5t$W77nynugT!06T{wT1-o?cP z(hiL}cYEutwALN9#`rdzz}9)d5Cvo-VFThXQLl1RfLC2oNm(+$AYH8vtQAlzaBB~v z)wf+}+BIHS6ff=bb2cEC75!gTGEb zcul>z;RGVINrJ!9LUf`atqZ;7mn*8+Vw^t#4Fb0Z(gscOpo$XUC=)qIi&faGP$^J$ z>#|4TgrQ48Loaxw;lcQvAQ!f%X~=;?9sahOal<;jCus4sVh@3XW3#|>KJdtl4PVEu zmCM6!R4prSGzcfV!HmBFPsG=!%~`sf%3^@Rr3^orgtnm=;w!I^Z%6nd#1Uk~!FrKF z3Q8A+vNZ{~h^BZ5nq-}zKs7Q~I0-Z3+XFtB$=)JUmtPI-j1*+_wdAcmjZqE7V;N+_p|0f_pn+a4s> z(!rJgc>^dxOV?{M`9iBV<%hW26QRL@C+>J~7jKt(+ngnH>_QSa{g;t+x1uftM*zVT2I(*06FWONkdFR4pLX$Tr4c^!MU zOQ~!;6sW@(8j8yhW3;C6XB4cWD4sa~sI+6(Do&TNoYi-WtwWdhDiTT)CDXucJh&s@ zRV?s`GE+9gTLWe2in{I?S`q}V(+B0?WkY6E#;*eAKhZCZmkodp*~X<+Rw~3^tUOZz zz}v<_ST-c2MNv`RG=k;^N9)&M9e2-iT}z^E{cxBKkRX8>+~tOKYH*EH*C?)U9y-m; z`{X$j90bNbG=vZmo>>Aqu>2@4O7BXRY&UNm;h8ewzKt1k9~lZ6R3x9os}aONbUZi$ zy#t(Y`3oo6ETsI*Po5CPkB-%r8ofx5M1H|GaTGL2r;L$*&z)Fa{xnbJhLf@DY}Po+ z>YW|4Y>9FHQtfb?6V*mK(Z57b7oYHa&GC@Q=kPnu%LiLC`BUSRsE&1V*A8>>b zBQ8XNZyo$}Fp}O6_-C<##rU%xp`;ETcKW={Cyng2bkC=AoAUY#1i zB7*^wLP<$+Pp?wAB%`nThRDo7#roiZLx@e4g{1= zkQvYpry71RQnwt6gk^k$>tA}-Vile0G;tXYxoXQcR!diWp2NDp-J#1w_(7g?)8H$B zT~R3>&Vvpc0sV}6<6@vR1eRvC=Q@qeHtEkkdpxTH-A-kbVL^wX#5}{moY*M%{HXF( z?=VNs2DEW)uil=hy)1PO^!Z2=WnwSjH_*E<IetA-hxm(gJcsmXOgjD~55CMp9o%z4fLouq zt;wX%h76Q)ICMnK@In%VDdEDR&UB?w3L+4B=Ic0zD@Q<{0y+Zev4P~5udsrLk4CoG z6poAG`zAD2hZ7UsWGIXjjes)okKwV69WddI6xp~4UHtU0E_uzeY#k7|wNdKqY%JUH z$Xg%4Or#7?QXU=%V}ysrVwY)jxE1H40VppqY@;WE^2j;3a6fXksU`Ak0=WlIk|M9< zd|MP#qSwm#4h?zf5#A!#6?(O|?z*F%KKXo|TfL?B&u-P373p@&T{uAFC&QT(b+wg~ zy7Ds42S?sLW$311+XCL|>SoQ(G5n$Ph;i#$y0HgEI!;jL{1^N_dxd9H=62Cz2YVyf z=#Vn*LnqlZgkzya#{wa#uX_>j*m`st26^CZ?gUccE?@j60j@YNXbKOTA@`NF<(goA zA-qeETzN|yFfAoa%Uay@{23r|CVz&y&$XhkhAhjs*9?D^Vun+7WP{$UPR^ku_kx;Y z2Lov^i0IxK_L;y4-NH_EjxIF#;j3rrf!i0^&P2J067YhS_D^{*YP_wHJdzATzQNS% z;AP__&VHnT``TCZEp}$Q^vX+gg@A8%VWJk1PfrDlC$a-SWi1+V+1A0~R?{}gxQn@u zdygK={YV1(L{`5L9;v^ytf3cp((fF~Ifl5NV6~PFiTU(}9jvtO1B64|xDQBuSbFim zN)QgeGk*qevhXJSm!=zYwCBAsu-_KcIF~4dTeEYeqt|)BoD2a)1S+?Tk`dm=iG_j8 zC&Wi^p)%=&ykTyY!Cxa8xEZ;8MVPn1&F!UPwMmWv(|kg8U) zD6RYvR~+h??=gwOiwt$}v0-u;%LzSM7TR5s3Qa0ic*&KA=E0k6s0(Phe`O2NrgYtRU7L^Mzws|^t4(ijeC*#y=IZ)C%aFcKRghIcS5 z+bCB)iH(19P9xIz8ji5VRN8bV5(&@nD(-_e?r<48waF%cV`IGXS2C!j>sHRm@`u)qEZ^L{_~;9sk~Goy8875Uf*GL&ND=+J-xh}+AgVKxW}cGRzv*Ewv# zXXej*qS*bSbXstEAWrfa$)Y$=Oy%&B-u5`J;GJiyjU~pr+wn@LWrYa`9GAzDrIC`S z?owcnSv{Z$=xDWhr4A$_Zmw~^d!_Qoy0fji>|#j7?M1Su6HAAC4pDe~tP@#|xH^lx z*X#$J)%z?=5Po=m9y?HLE9fY+QVGyP88AHux`7w)GxSc<^z>Bi*}XUU z{wiOY3ldp%SbG9LFjp$jBmYSA3VyhPRi8QTgNEGSB3N;#5KN@V;@}jCa`(m@?|1`J z?1rk5^msl@3M-%-!c_vK8ZIca5xdr`wn*cELC0xeP&NWOAa!?05Kegj05+dVL_t&p z$CEKw6-1u#l_*2VDx$wK*QnHPtp^S_(FO*_Dts6ZOckxG2DX(WHic;&prL_f>{ytj z$B9wEEPY4-js`=>5E!|Ax;C#y)rp9gnRV#=f$=@)8>90&nOeSuAT`pIx4{HtRfRDB?0~PX=r;4S8+;>8^%s% z9a|cP)a$i6HAcM*UEs_!PhbNo93aTw%+cW#6ea)Mq0xc|(Hl6~!G`#mK^)WpG#t8} zI1Z>dB6Zs!LdQbg@`=OW@CV&eMk!DJ9u|hiC_8y2&2vsPNjXlX!17FZest*O5l%Pq z-s28`&@RhUFJ+Ynsag8%ePKaEhC)Hx8W4Qf!RaV-ng`}@CA!CQvGFzN%dnMR5=eej zpHvSVoh)n%-!cyroh5zPbEJH#NFhfl1Rv_x7D#Z3;~~WP`>A69EX1vG{#|!JL--QXf9DM z8@1}^;yYwP>zFh(`y|vJi%55XASbOVOL}QA!)!3}bp<{*l3U)s&PPObonijfz#$~) zgg{7)!XQ4bZAIOZy*JX6i72>&fP>wQwi?-@8rng{I9%}*h0@WqdKv`}6*0^ux5E!U z1ZcXVh|Lk@b$3=FkxB!lN<^tK+US?KT2?0}9E7YPkD)-WvK!;&1lI3ENa~b|7(vGH zJlCkmKu! z4YUTMfs@msWQ^^Q;#@$KHAd_`oQ=vW5%6!6MqVsEGMu5I(fGJKu8mrthPLLH((7fu zatXY_#jNtqYa3)S8fC8@ou>0x4!^|ExVNPfZm8f0%#;eo$X^*_Y`JeVu5+~)#yNzI zMkdO(WoRQA%(*wDTYTc=tdep;jif7ES8C7r-F4Ia59>?M9IwCkuEVv+W|T|oz^1WQ zC({$4bn6*)!z-x?aC9as$}n1Wb&I(b=U-fP=Qy?W$I0+5dXqRpCr{tFbf%6RWJx>D zQ4>&_>Ybm)SqHpz;GWJrgp&h$?NuYt<;0+hoUcg5POT8Lc$Pv`DCTXf9!GVI~>FmKl% z8iSYk@gcX3pV7MV>G%3@M%me!$$H|%R^5B&It>>KAwm?m(Gz9uHN#re3H^bOjJ!Zh ztfIPHPoFpkdg7%`^S*bU7XJ=CuFs!1U7vj7@jCqQ$LrA@Q}r;9GVmt&+JqkEHmn&S0Pr$wOmO6zPkkUXBMtIYL;jO-qPpHk70-x+{U(6vq$q}oqQ5xBx(tked{gD6CZx4Zs3Hc-5IK>TcloQK*ja@lYD?CIx&Mv7_tm_}AM`91T z0WhNlDys}1-NBQ`*%}9@7Y6{Z+{wUvPR9BKzHcmaoZa9+9yb2e3eM4KkuqIx6FV^7 zG;vn`^04zH2-Dd{_iU7a!!3JL!f+VY*9A+56*y0(-rGBN&cYR3p&OkfCpeLpy1H5s zlHZMs^3aL*ZpF&&JPhiRt`S5&232H0f2?~6&;8eqvaz8LqZ;fbU}-pw z>97$|E;?e^CCvbd`(|luUd>DclO9)Um!HA_M}EbB)7791ExQ-i8hFp3pR25rTcys$ z(VLQ{KKJI9lr%sI-{ft3j`V`*1&lB>*$p>ghI`i`!VmHc$PeZx>ramzsxK~|uYa_A z7hAG3d`WM_!Q3=>21NQIbht-P1Py-R=urA7^4Vgj)L^NXd_x1(rw3@m98{Dkl7$hl z?}i;}%sYEXoRZ_C5S_F#s@JJSc@WOf4*IT;ixm%3m>a@f$U1OAVfgZ*M&_Lb{Jv0)5*R-ij z41M5Gbmm5=Ivwj1u93+)O9|zI3Y`^FO#ms<&JhFSH;QQkCf{<&ed}+0*PIBBJJDTO z&gVbu-8j^wd0Gqj$h0Uhr7=_|KU0Te_Tjl@pj_l?z89{+Cr!SffGMAdoXxPl{iIt)n#bMSwQVXg~nGj(BQs^)Q?mx)Zj zKva3@*{{@|Q~$KSyLzskrjeSmE^wA#^Rylpm#)-To_w)(&MszEjty@2@Nj^@$o!Nz zIV1-D;emRakMh@fDHHk4ZGK}%T=*9oN;15oj1E=*9Jx{|IxKUe+sc|#bsO1*dC3En zc%bwsDVz;Y(amujYlh|28TC4Ob@t$7fRK8)CvNc66+|K_73ndOL2gi(Z%eBW;wK^6 zKlKJLov97;Ci4&rb>hXd?DDR@cj=Thuz)`2{SpWY1Ngz2M%P+&DMI#$W0s%V7jPW@ zwox*NL(H02V%J%}SGDnCX>sb;q~9 zRy+UhXKL$qcHR85$7&~CU-X<|ilM@3h&VIl2|YTJtnfm}!cByyB5DP1VVj=W6Zjqcw8n$@=~V z4aM@AdjHAq)UV(2llA$-zgSBbF4WDhJyjbxr!n-^m01hGdV?*--*p?WA+-_WFHY2O zQNhYDa0?66`2nvZPkF$#8rec5EBu6DQ%ILbH1NO(C~x!WhbYYlt;$7tY7025M9sm$ zx`cpSC_Lg00sbimB4eErmx!coYPh+eJu;F74JzR@SDj!FZ#e8L9?GY2mlp%aXW>CT zsSk{$0)@tJ(rnY~pMYok7AIJ4eU4?<^DKjfK6%5T?A6BLAWfk-xAK)|%o-xJlb?^| zOH-EAhY!L+3Jz5ffZ2eo6X~Bk_Zk}=A|q)N217?`PIwrYZJQbOfC2F}40*cCMCMPi zBg^SVPAy!m<&QmF$G-LdvS?$Wp51+0o|kL8gih;lsd5@-Xa~{wilzcw9jD#uZQZq(<)ABUrl z8YAbbgSMqXQi(De$roKqYb96YcDY+ip??s|E#V4f!2`z- zk0~#?4>0nWX77%RI5N)~tL7a%uzoPI;dX0}?zQ0bInRN@+xWzR>9{$hNfTN{++eVV7q7f*aOE4hwO@2{CBKhIVvOH_%2py&)VE04!` zXz=;4OCpuWp@9hQzhtB@k^cAn}&90j`I{!!VK5_mo{;PusP zcO(BqyDcJ%iX$H25qNY^gG~{*A;3W(f~=91ft2-HJcr>tM^<$yIpwWRrqT0M{gmee zxZo$8UVx#Vdq;RQ@AzI_zhqm1JyMdP(OlFG-Ffy4datn9;Fi5N)eEn`Sj$|yZF!2} zROMS>B-o%RKy2I$5Im0#s&nW?54~EuWu%3VdIyfNF?`4bON*s>3y*&A=J}c*WrIX_ z&QSTHla{9^gdfPXJHyrE6BzLN@4?J9_~fJL#pJQ-9O-|jA3-5VDbUm zr_xQ2Ge%&RPeVmL4$qj&Nj+5=j2eap*woK{nDW7viD` za&W5C)X=g7HuoH2;$bDCXt!z4)69c1y6QM2%St=n-FQkTc&L~Fj!TabG_P8j<%H#p z@N{NmPiE&SpeaU(sRGnj?@Cj5rLBb$z%x%rTdBFJuz}+*P6xR=UY?$ivP~HQ@(IB@ zB4TTMTS*`cpfPddKn>T2Ds7BKE)EWPCu#?8P~ZS}*&JsrAX)qM=xvVyW-?dCte{z`|(lgs+U6)orhQx$1Fx9v}H^JxxRN->&}wOJlFp_a6Em z>&V%c>u)~uZ|a4Q{8BCRer@N=9Qxb0FjZgq(JOW9fz6s@KdvPnkW1sQ{DTLMTw0?K z;WYQblltr-`4^guQo{o?u-UsKFJgvkg^vRI_pr1 zjW607zH6I;@53|mx)?;KCBIFB-}0VYzL~R6v|;L@E={l_c1ovSawgACsEf5 z)FGE>r+1{zz%K*Ib-EpwFRx*@K*culng`2CXZ#K)90ndb?*xUw?)eDs$af1c{De^7 zMep-g__K50yK54={V0tQPr*Q!tFFK;_{kf1D?jD2a6_B?09JE{U*Rez>C^_1fh3w9 zo;3R$M!Qi*Q-7nNzYVxyl1zp%I*UYLQAmhaKrqL&p77(6dzmv(f-v^lvc?Qe8NKL; z;UY$&zGx5%&?qMf)L{d)!b@s$7=fToQP>pF!6`KI^E-+Y(NU(s(NS|p*{IlU%oBBx zEh^rBg_p?extEFah59IC+w5gPU_HXii>B-R>MQksf9GQDURMhmU-AR7p(nE4|&_Z%N~SV5SLn+>r>4OW}@itV8`cd!6PjpNr?~CvN`$!&tn z`4_pVZ{Q+|{t$ITJe*b+Oq9(*m9M}E&(swL-ZJLWd6b>^S$;+yhn;$ucO%ckl-NY-x3*Zr;asGi-P%p2N8yS>Vw+Ng1xgLm_e!-2CR; zD|-sc)f(5JYi-?N#hnlwwnFz3z*gSe6yF z(2yV%ZE^cl0oNmT;B82qj~ZFjK{FOOr%qM5ORAW@65qC2wCPLejz%uq`&{ zMxP2V*?}K<+cnprA%ICHCOa|(nnN8~0^EX$T`5ErIweWR7*+0mh^r081+2oIPjteN z#um>Ba6N7PZ2e+HzqvkHNB147m5oR0pMLwp^XRVxP9~_tJ=}^rZ@5d#-IR#!T ztGHr70Hx-T66HeS>^=4BC>7gA()-db-sXm;49SV1&VtJ{$SS6AO_yg}LT7q0@{96v zOJ!Fk4u$D3+DMznV>-Bu&vQ+^*OYj`vy_Q51{QqHn;U@u^f)xQn>>SxOb zws`P0oLlrrhI3Qm6Qj{W;gzy+_(elb17La@GaH6>xYpCdJuT~gLVn0Uz$m2wpPUyk zzpx_<@XUdJK^h0H7Xhe?OKW3wc<*iWAWqfeZ~Tz4>wUF+=QVZ07AR&N8LA#d{Fs4+?x@-Nq?4e9 zbfp;iLxw|3nPgYzq}QH>iovO@)&`WQ<<*0DG;PZURth6Cv$ssrcyN_Ql3~{L)SWX^ z&%+BFnE(tvDJMg?m%)5NW_XBkJ#b5}mRmM}(%Q~EHItD!I(wPTP_U6Q24Go%W}}s4 z8*1#-9*DhQWtK(&8eNQ$XCuIUgGP9k9c)iuIA52ico7pzrB`{mTy{f%2Xdh|9)u~0 z-h(JK!7m$C^pQ-|iEhK6*f++}`Yp`{656`MYrM2+^#bxQ?@-+d)2ut9-^otfVJ=QNHMU9%QCo^+Vmv z!F75}iF8TRH+sv430u)-ZqzYUksAg7$HIkuFi2;l@<1<^;Fw}Git~2%GKx=_WPVXr ze4Hy6(q*uMbZE(yA;T_pCiVnO#$KjN8SayS(0tgYVW))`27f)7iI`PlohS`a>~v5L z2~wAnXvj!i2c1p6G(2~+HJz9gf#L+IYGZq)E-W+ddC%WLDgL+~f04JW&4RNH1GMO_ zArzE!l;Wr3(D9Kjft=foZQJ9VbcYD+y2}l_&GH~0>lXpb0>8@(6C_Qt8e0;1NG~8g zJFd2g1iKMZsX4;ff#Vi7`MaBJS#V=`N<(xrz~Ni?`rR3;3X%NClya#QM^3^P#Pd@c6NuALoUfUjU9LUMnVi48NkfA3#eu4# zp;;YB@C09!Q{;dy`9LavI@?6j*`QOtfU(}e`8qIHQu4uNw`m}g1})x{Mr~t|2ail~ zDIPR1p;@~72(NLt+x8|XCm*tU${gxMe%$S#H(0e)qT)C$&XIAVU}US~mKV`)*^MmY z9Bp_cLEJEw^ga?vx4tX;I8TbFKJ^MdqA|5=cBU>~KEq~|HcXvg_-#khN+I$(tYcYM zk0n5`5wU)lt~RS54sk^v3_8VCFx;G_{Dq~7dh~}UYjfP-kjDc;mo&(FZKU-(z+$Js zM!&k(0N2!C;SCt8<8S{#9(ZM=V58IxybX9`rCo#;^r8I$aDeZFmI6#;HSkHm6AP}n|!jx5Q`7kAq6 zt29xWq$ZkAjV6Be4Q5V^}d(zfLM>ZIX?zw_?1b_h$WQv#6 zB@~oXDwhP|?0~Jyn_*>&Ml=JwF>nq~xu;+0h6M;BmXZQg(FP7O$|`u$W67;_+0P4D z)-h~krQ0X#Ws5zIC@uB-u(OSaPGN*bdAKEpbz9bGZH&zVtx zgXOF{8TK&GARmpO%W#cIZ4fo~-q(!!!DD-y`dD8dX2npIiG{5V#P}wGUmG9(tP`5d zbL|s;idN&Q<4L4SF%35whOZ-+XiMRT=LOC1KH;uijy)-tea zMHPFIPw`O}Bn^40vz-R~0iT^4j1It2?{zQD`n6T)4jk*(9|0?Ywb$goL5GrgN00Ox z2GNODnup<31ca|&GtHf5WvZ!(YGkgrA`BQbFeJwjEt4@$@4~~Z-&W8>+;Ij9nJ%L( zyiPG2A}TzXZVNt2UD@Oe&p5Kyx$=~|h9mGeDikR=D=3YgU!x@;Si)?ikPdrMFxRuQ zhNQ-??x{n&aV{%swS$KblZAUKz9QD>#KPW>kyn=DSzqWSopY6P3>sc%O#U*4wCDI^ ztOf%5JrD4H?}<9ZgHVpEPOw@-1BxL7w+~>c@YV%JQ9{mj{6X@!h`nQ(_zDl3|B?Vfp1MldMdjdfliG(JnLJW>jZUHQ#27wCW|7FdwiaH zQZA4N;FKbGz+@evS2?0HI!1+Vx#lMoq<+gJE%FjqD}q>fr6D&z?1AYN)ED8%A}}Dz zPGfII^4n-RKetZr^5n`i}dGKgr;^+TbDvHm(2)&oChGiIF)RjX2Xs zWsWMnV(RH$A>jZpbpm1^T**1cvn@vE;;Za~vO^$xDS{giimv#ZAOxKo8)eL%I>Hf}4PaV{)91x+~D|hlao&qq-sAGjyDcUg2MXqYjwY<=TUDbu14^lJ0pQ%7=zc zofbZP92xZFGR$n6G5`^KvBW?kMw-}(8~H=CauYAKf`-aEecoe>MGSKjtGq8{30=3> z+Ikv!CIA&4F!%_;-4yr+HfgDWdZ;UQYd}Vjr+8gv?IMCnhEmU-TdR-##8lmT%irVG zw9KW@^RsAr^s#LlfBDbwB8^|{*7-sH#?sJ3KX^xc*g<+jqQxqcSiGYk=F5;R>DoH@ z4p$!Phpg{f&_rO7w_{2J_1cEB2%K`r8a{TQEcd)29ON;enZz4%D7JU>a6;8_z>MM1 zNMxQtNkV!i>tH}0f`cJ(EKy$WnVn-a`cc*6S{>ztYju#A_**jA&G92*%##m#F)?-B~p3(z1qbi4IY*@x{03@Lh z;-CP#krH!NW|b(#cLQv0E@J?&-Z#b#&<|MQaUIa$oH>jbfI}$M3!hYpZR%TK<<9T3 zOxP7X>t`?3*z#8WJVxO53QqhRxodb70$}6ex%KG`H74uSxVBgX;0uCWoS*~!|JpmV zSgVRG4A=F-ys4g*FswcS4_^MoJ=r~n!iQ*43~3#uLNIV(lbtSgmWs2Hl{`!{gCVc{@rZ*pKaD2sG{XzD#4~F#SqI=F zzm=Et6p0bvVUTt^>SIqG`lh`evQZj%MrV;5`K6bkJL1Q3+wiMiih6K6P#!`y;K;_a zIc;gNI)jnJ4oJ(EYJ}0&<293<4t;?Om1#ZXTzt{_iZ2{UV*D^GI8eXbhZl7uu5a{A zHjRxWS+C$%xpW}FmsuyLc_y9yg9!$LwgPR*$`q@eWznL}s34}ff6_udQ&~j|Gu>wY zU}0JsJf}xx3OIIc&Z25es~{21ivs(?)rnfJR1SVLWui05C%7 z$0sNIi-vk}uah_pNma2rr*ie>;WTXJvUJPhp0w*bAElp<`%^kayQx?t%ixK`%yyMBKu;mBR0Hf(xj%|F1{yCFY69`#YoX0;Yl=^dqXgibD(}fNcG06`BjQ} z{1Mu^f>$FNgq*hXj~tn=cLIOt<#U7+*=wxFe> zr&kp3ve*V~X2DCR;t_srScN+tEzd9Efv;vX<$4qirc)i@90pXr3WfwuoI5l01?I@> zx)N>GiKkC!ztT_ZujD976^(*#+71|~Zu2ut2HsEFGi!h`hF|mu9n8;bsPmEj#5AGv18#HKuric0bvFegVa_j(*i9)sm;zj&paJ^d$ImCBvOOk=I08b~mgH=A2g&#=g zFz=hWY0MD4g_qaLD4lgwc#DcM8f@{aJSx+pEMaS+)DiL2snd!Nh=Q9E>yEKu)Z=fm zC=G0bC;DjmlxO=#o3l`gMh|FHq9Ag46iU0+s7dXI{MIk3pAwYKbzo&8uKA&*9SGh* z$3Q)~tHnn=Qw$w2cnczm2uf4{jg0wsDFO|FI4cNXOCeCa+$eQR73;MSfeL)tfN23! z`~SK+2kQlX>=3~t1Z65%`B6Mw_V4UUBj2Bz`mGp|zL+>J9Xo4g+UwxGG*zSzYH65^ zQFUj+G#GrTe2j>ViGEQsUOU1|ZyYCOu{=^9Jx(Zv(AHiV8{cd8>11uK=O zr!dx1!t8aq_R%US3UcvJRHc-B2(?Ymo_v4UU(#^x5^3vMlm?vm*VHz6XzHgYp?Q~4 zmXFQ@gT!=+Izk$gIOJg@_3Xi-*{3kDQ87*_;SN|zpS~_-3V=#@d3MWE$`0$iHe#(J2 zA9bf#1z)q6exbZXshC1tr=_-b_E>a9xFSW)K z*&T1AmGz;mGsm>8_LB`N3q4Y2s-kmS5j|l+by`+W9f2h>P!hBy8F)Dj^q{S&P0Gg; zSD=IL&@|o8!ltvozG=ef8GS(Ml-K%-9w^qll20S8FtFH_Tm-Tqqw+aD&P)H))zvR8 zSoA|0wdV*uzqM2PR*(91$R4?kLXeKac;~X9K?g;hdHt16-|bE;ST5>@Qe2XLv&?x4 zvPMctkr!S7OnH=-_K}0?UU7b4*iIBlsV^QZ3|UnIvdXz+Dw%2QPT8~$ed&-Cn0poq&O%jm85u84wagfSzVZ>e-LU(8?6iM~0U$;He&-eb7DD2>J3DGzaT*lRsfd zP)H@^WpDX?OKLPq$2P;PsN6>06lxh0J=a>)vllpx>D@{M-()5Gg@;P|b z3Xwx$L_iA1&_9o8Xh2|zkyl}|{$`JYoff5)r$vWBG7OFaBg8h%H@jYx6c68x*@_L{ ziljA~201VqL}%X32OT{3g%#zm*7O&9>@i;+rKv#fzzO$h1fJSZ1L0W!W9m9nI=pMq z!#j&9LUQ4NK!CI+7DtF86OE5X53hLqYUCI*Q+FH*i~BYZ=mvk(w(7_f&=ksnTa`qo zrlZd#G`N^I<vG!WL+dJGlmGCCQwD8q`RI@ffW-z{cdL;{Qu z1tX$AKS6KI@x+BJyO|_!yLbYA*MmBW{EUw>+5m!;urUps7h#WlqNfYX!w41nu5;b7 z?CyxN9ElxfGUPd+Q|JeDRfc$XCu(O7l4xMoJ8r8mr{{$FAy0lzL(j9Y_G4WTU+x$f zO$l!~k?2Nyn|5LKe3u4qD`yB<$q(D;lFC77cSI`BH!$ex#La;)<{1nxN()CjPB5dp zv|jBl*89*F=_{)p>8({CiwnL74>qj(z&GV(ouKY$tga^WpnKG(WcHdL;G4F7VXKhS zM;t*2!Y{ZhK^|c+1A{!`NxDPY_>K!BQag9ZW!phoZA4)NXIeR}pIuVg0Ud+V%$YON zKmPtVmC^hl8O?q3DX)iOZXj*`CVF>wciMGVy~v5HpdmRXl@Ejo4d;xvxtreK&ju6?N=U%Qw!gd<-Zi!8o$zWN(QHlheL6b|~! z$Jz}Wkgl$-H0ALrse6y^Gh2@|BsbWj5Q+Bhn;Lu*|CG z`zGC+&N=5CeW*yDx96qu{aTtLLtqNu@Dg-_g^Wj$S7oftM;$*-sB|H_2`SQ6kRvo8 zum%H1@nb=X`)e^loPyKj(_dVhh+-{&k+7ga6yEe_pM9QQd1ZE*FyUP5hIbLxr-x{L zxlvxkaV&T*3M%ALyyS$Gr^QJ{hnR)UdGwq4528d6xF(Rh?w-pk;(d!UxK=Qh9E20!ZGq%S+|v{UV~Zvwe#+~@7_l^YAqWh^=_-(cH1qD967SJjP#Mx zhYuf-_S$Qdz0E$tQp?z2lgFo@ep=~9Q3JzAjIbTuc3ZwGvUx*GI*=G??7Q#2?m-dR z3$eND+`I_1xHLLbhM;t&`|qzOm*p)4J}qXgCB+9Gj7q!ouo$7jWVLkz9qG^;4!~$@ z451r%-J?d0G6Y+*ZUi2T(j%-3k4Oweyff>%W}bCTXHrjNOUI|Td?E$E~lQH{!oh9C(D`44E3GYZMph`}(mF@5B-6?FDiCug7Cu>FV?Kb@_Yq zhfKZsZ=?r};u_^`6xX}VjlFKaRAFN?{hst81Ph~&KmK_7=%bI)9((NJsjRs7d+7J| zyg%w)N>4rYRC@dEx6>z|eBw2aewx#L>7|#_^y$;R9yFMjw<*k=IWx_eF~f7cg9h=Y z*Iz1BUzfKLe{7g|;)y5HoH=tmcgvIn4dvZ;-*x=JfdkVB&Fd2P)?06-C!c&W{Xsj< zh7B8La<9MsdV1!WXM8m-3~kK zpdEOt($uL_(;IKR;f*Jqot@r%^7PYBTc21niYcz%hNn?I^rg7KV)^pr33-r_fZWJ3 zckbNu=%bIi(_meUxDP-4Fg^CzW0raQ?YDOvcwtutc4Zw3x_wF34dEsDo;Povbsc?R zk(_l7>;`&39Gh=;-g#$}eBp%`(yUptYW9MT<+`(y6v{x(&wLl?s~uO zy6db5lO|0{^XAV>-|8!mH{5W8<8HtG_OxKZf<#BmIXZOnEjK%^>}>}9X03S7J@-sk zUU{Xf6-NuX&N$8r24GHWwlm|q}z%CSIa3<3$~N?6C=DkNSS~)mPI=C!OS-SH~Q4jJ>OO-gzgTdFGiO zr5$zDQR(B4KXwO#QKjLG9Xr-I!A#KkO^mfD6L{byvc+=zxbcb6RD=O~J;#6b?d%s{ ze6hCC1h0JDxN+&wLl3pQ-+c2;nm>PjI`!036ZkAzw8$NA;2gBda15RqT^w=55guVN zGl5;e)5f`?iui=Z2$v!?z!h$ zRz@3OQNK--HUmcJ2$&GSBhJf+30NQ};pLZKPG5fcrFUB~x`=6AW-QPfN2dXH^&Ys>mhU`OP1)(2r`)HL^u-YNpu*|j_^7I&yvAlb}^Rd#V4BMfA-mD(`A=k=6Ke= zLJzPT)H!(3u@Hy@JK{MfF!e06-;$9trxmh8X&}IV8J>D&j4L=$tAUp0f+0s z2Oo4jvw4Ci^66YzGh@WTg7Sn36Wr;d8+52l?@XLHvF4bTt=)tzWNqWZ3olF;U38JB zrKn>@XUtr%PDZD}R37$X^5n@LmEv#^N1*eG&X*;FX6r^vw((jr{jf+(#WF{?rH80E zDx94nc$2Jo#8dcq(|AoRnujNfCx$nHQ9wb0M%l4q5uLh6CWh5465<_w{q@(L?`GaN zc3k0UV;s?u@F?-L*_Fe*Fw;6^#U=R0nPAu!Mxn^gySI9JmRmQ_1s2WOxw!YJqx>>tOvV-32^!l9)dNV9|} z3!O%lM;xKP;jb&MK22F<-cUZnvbq-HRIUQ4x||ZR5U6CDg-J7Dg;`CKudOEwMNvc)Cz$LfeL{Nfo395bs)_| zv!YcYP$5tuuoWRtbs$?&WmlqB2vh??6XC{H%$aJlMAt&pujK&pZ+2mdKI;Vst05cnx A=>Px# literal 0 HcmV?d00001 diff --git a/docs/img/documentation_images/visualize_click_count/with_totalmask.png b/docs/img/documentation_images/visualize_click_count/with_totalmask.png new file mode 100644 index 0000000000000000000000000000000000000000..e0541d70faff9768ec18d7391bbe5f353f44f498 GIT binary patch literal 192838 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 =7z~}%%bU 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%0A;`$qY9ep_H*3hVOfg{G_xGSk||_{G_UK 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
    R#f+O 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 literal 0 HcmV?d00001 From eb126f829bf24d1e297443bb1e704e21893658c3 Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Mon, 26 Apr 2021 09:34:14 -0500 Subject: [PATCH 019/178] Update tests.py --- tests/tests.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 25596b7f1..d42549cae 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -6372,9 +6372,22 @@ def test_plantcv_visualize_click_count(): e1_ = matplotlib.backend_bases.MouseEvent(name="button_press_event", canvas=counter.fig.canvas, x=0, y=0, button=3) e1_.xdata = 0 e1_.ydata = 0 + + # left click + e2 = matplotlib.backend_bases.MouseEvent(name="button_press_event", canvas=counter.fig.canvas, x=0, y=0, button=1) + e2.xdata = 1 + e2.ydata = 1 + + # right click + e2_ = matplotlib.backend_bases.MouseEvent(name="button_press_event", canvas=counter.fig.canvas, x=0, y=0, button=3) + e2_.xdata = 1.2 + e2_.ydata = 1.2 + counter.onclick(e1) counter.onclick(e1_) - assert len(counter.events) == 2 + counter.onclick(e2) + counter.onclick(e2_) + assert len(counter.events) == 4 # ############################## # Tests for the utils subpackage From b9a1c1d7afdfa2c786dbbb39ddb91293f865eeba Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Tue, 27 Apr 2021 14:41:36 -0500 Subject: [PATCH 020/178] Update click_count.py --- plantcv/plantcv/visualize/click_count.py | 36 +++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/plantcv/plantcv/visualize/click_count.py b/plantcv/plantcv/visualize/click_count.py index cad392226..93e94d8ab 100644 --- a/plantcv/plantcv/visualize/click_count.py +++ b/plantcv/plantcv/visualize/click_count.py @@ -7,7 +7,7 @@ from plantcv.plantcv import params from plantcv.plantcv import fatal_error from scipy.spatial import distance - +import json def _find_closest(pt, pts): """ Given coordinates of a point and a list of coordinates of a bunch of points, find the point that has the smallest Euclidean to the given point @@ -59,6 +59,40 @@ def import_coords(self, coords, label="total"): else: print(f"Warning: {label} already included and counted, nothing is imported!") + def save_coords(self, coord_file): + """Save collected coordinates to a file. + Input variables: + coord_file = Name of the file to save collected coordinate + :param coord_file: str + """ + # Open the file for writing + with open(coord_file, "w") as fp: + # Save the data in JSON format with indentation + json.dump(obj=self.points, fp=fp, indent=4) + + # def save_counter(self, counter_file): + # """Save a counter object to a file + # Input variables: + # counter_file = Filename to write counter to + # :param counter_file: str + # """ + # # Open the file for writing + # with open(counter_file, "w") as fp: + # # Save the data in JSON format with indentation + # json.dump(obj=vars(self), fp=fp, indent=4) + # + # def import_counter(self, counter_file): + # """Import a counter object from a file + # Input variables: + # counter_file = Counter file to import + # :param counter_file: str + # """ + # # Open the file for reading + # with open(counter_file, "r") as fp: + # counter = json.load(fp) + # for key, value in counter.items(): + # setattr(self, key, value) + def view(self, label="total", color="c", view_all=False): """ View the label for a specific class label From 993bf27141aa9b50441633226931b1240ff27369 Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Tue, 27 Apr 2021 14:41:41 -0500 Subject: [PATCH 021/178] Update tests.py --- tests/tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/tests.py b/tests/tests.py index d42549cae..9d23fdaee 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -6343,6 +6343,8 @@ def test_plantcv_visualize_overlay_two_imgs_size_mismatch(): def test_plantcv_visualize_click_count(): pcv.params.debug = None + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_visualize_visualize_click_count") + os.mkdir(cache_dir) # generate fake testing image temp = pcv.transform.rescale(np.random.rand(5,5)) img = np.stack((temp,)*3, axis=-1) @@ -6355,6 +6357,11 @@ def test_plantcv_visualize_click_count(): counter.import_coords([(0,2)], label="total") assert len(counter.count) == 2 + # save coordinates + coord_file = os.path.join(cache_dir, 'coord.json') + counter.save_coords(coord_file) + assert os.path.exists(coord_file) + # view different classes counter.view() counter.view(label="c1", color="r", view_all=True) From 91add2e05ece2e3b6053a4fb930eb4880f2a265c Mon Sep 17 00:00:00 2001 From: Hudanyun Sheng Date: Tue, 27 Apr 2021 14:41:43 -0500 Subject: [PATCH 022/178] Update visualize_click_count.md --- docs/visualize_click_count.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/visualize_click_count.md b/docs/visualize_click_count.md index 410501356..75a1b7cff 100644 --- a/docs/visualize_click_count.md +++ b/docs/visualize_click_count.md @@ -24,6 +24,11 @@ - label - class label to show on the marked image. By default `label="total`. - color - desired color to show the class. By default `color="c"`. - view_all - a flag indicating whether to show markers for all classes or not. + +**save_coords(*coord_file*)** +- Save the collected coordinates to a json file. +- **Parameter** + - coord_file - (json) file name to save the coordinates of collected points. - **Example use:** - Below From 926bdb7e01bb7f8d6791adb438046392929eee6a Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Wed, 28 Apr 2021 15:14:43 -0500 Subject: [PATCH 023/178] add documentation for detect discs --- docs/detect_discs.md | 51 ++++++++++++++++++ .../detect_discs/discs_mask_scaled.png | Bin 0 -> 2726 bytes .../detect_discs/discs_pre_scaled.png | Bin 0 -> 6692 bytes 3 files changed, 51 insertions(+) create mode 100644 docs/detect_discs.md create mode 100644 docs/img/documentation_images/detect_discs/discs_mask_scaled.png create mode 100644 docs/img/documentation_images/detect_discs/discs_pre_scaled.png diff --git a/docs/detect_discs.md b/docs/detect_discs.md new file mode 100644 index 000000000..6a9085039 --- /dev/null +++ b/docs/detect_discs.md @@ -0,0 +1,51 @@ +## Detect discs (developing) + +Detects disc-shaped regions in a binary image based on eccentricity. +A value of eccentricity between 0 and 1 corresponds to an ellipse. +The closer the value to 0 the closer the shape is to a circle. + +**plantcv.detect_discs**(*bin_img, ecc_thresh=0*) + +**returns** mask, coordinates of centroids + +- **Parameters:** + - bin_img - Binary image containing the connected regions to consider + - ecc_thresh - Eccentricity threshold below which a region is detected +- **Context:** + - Used to isolate disc-shaped objects of interest in a binary image. The output mask can be used for further analysis and the coordinates can be used for object counting along with the class `plantcv.visualize.ClickCount`. +- **Example use:** + - Below + +**Original image** + +![ori_im](img/documentation_images/visualize_click_count/count_img.jpg) + +**Mask generated using binary threshold in the blue channel** +Note: For more on binary thresholding see the the [Use VIS Tutorial](vis_tutorial.md). +![bin_im](img/documentation_images/detect_discs/discs_pre_scaled.png) + +```python + +from plantcv import plantcv as pcv + +# Set global debug behavior to None (default), "print" (to file), +# or "plot" +pcv.params.debug = "plot" + +# Apply detect discs to the binary image with an +# eccentricity threshold of 0.9 +discs_mask, discs_coor = pcv.detect_discs(bin_img=binary_img, ecc_thresh=0.9) + +# Apply detect discs to the binary image with an +# eccentricity threshold of 0.5 +discs_mask, discs_coor = pcv.detect_discs(bin_img=binary_img, ecc_thresh=0.5) + +``` + +**Mask of detected objects with eccentricity threshold of 0.9** +![count_im](img/documentation_images/visualize_click_count/count_mask.png) + +**Mask of detected objects with eccentricity threshold of 0.5** +![count_im](img/documentation_images/detect_discs/discs_mask_scaled.png) + +**Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/master/plantcv/plantcv/detect_discs.py) diff --git a/docs/img/documentation_images/detect_discs/discs_mask_scaled.png b/docs/img/documentation_images/detect_discs/discs_mask_scaled.png new file mode 100644 index 0000000000000000000000000000000000000000..357fc236abc29c6c60fea4e9bd0bd619c6925514 GIT binary patch literal 2726 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 z zVZx;4GO`i%1^{$0ygdTIN%I+XRDz{;bSzVZxry-zpgUfE5}&$r9b1Qhwj{PDr-8cr zwiw1e2G;>{2!4j3I09hVD47w=;8i_PSSa&>8PoveLCXU0Y1FLQiab0^D_C9#T$LGa z27}M>S$a;d`;SX)wVp?T1exPz;LhtH^1{<{`TXA8thQt|QfKlAmD38u>o;DDPXQ2S z<>IjVBVdI1oDpfq@NvcfK8x>qE#=MRvP+A)qo0;Y&}d=Rn99?@w{h`3>woeWa)dmD zwR~Zvok1)!$90~|Uu6AHI)pzIGM22@6B|}X&+k0n)j?)0kH%HlA}G~{B+0Hf9cE3{ z_DO;M4VDNB{N(pGRu5Uw6G`7&%qu!KH|;19)}Fe=4plL3wnS50uGg)Tyg;e~*a-kw z%mzU&KHh5A-)-}-Y}B0VS>HXZy6^%i1OQ+K6Y{8k3HtMNtO*;fW3&$V!mh>7b9b=I zs%^lI05<;u;P(h7Sg;)cHs5c^1Tm2X0ASal&v*baR{dfUHd5HuQ)ip8(Ix=^JN2J1 zLCkmnz#e;wNUG@QVhLE2soZAK4@&K&;bDa zEy7fT#b28Kr@uBPe!al8E(*2rhbsWU;X|+Y!C8T8RG+djidImD{?g%ccf$I-;O=k| z>Ei3VhAV23@BR9$^5i`V&_JQ=nMe3#|=$Ya848fzeSY~Su*R$$f zXWw=Zd9-v!vHd6Hn4*RXYU-BGcvFQmAhJIg7cvFe|aoiZb=sD(VesI zmmTD8R#WDP$neb78)$9&VtA~I?O^-?tk9=W-YYxLk;dmP63auD|KvB%L@?n|_H>Bq z($mtbQ%{_9poj;6DeTeJ&}6@LV3Xxa0LWu631utfzHOB4sy=%iR#EEy`7Qu2u}0Pl z0MN_GL(Qxk<8C`{9~l9lCu>9R@Ja4)Rjnwtm!Xh1|^-%KW=kR^f6BacRU;4P79XmI{? z55q|vB{>ONKkY|EL_@bYk3ePtIK&uD=K%oEtN*W8t)d&zd3~HlAPt|uhDdDuVz3Ut zxqxk7m)=?MtxbXW0wepqBsqz=#Upn!8xwu^5>M?Z>k4r7F;14w<;|xIx4yWb|J?n% z?gZ)ek(~hAGC@h}xP@mg*Ce7&`+s=pFo3ccf(xuQ2-n{C{r0bGNOI7c4rHxZ&Ky}N zH@S8C*_z*~Y8?7hy0eJu#H9W-h&i(6q7Y-sE_Yjz=VttM43qriKAO3;*FVo0j{kkE zIKN|uwySdq1ea@BIB2yKWpK++ zcDl#_T`W9KaNSP~>QonO6o_;;Qx018TGX*eqLAleVYB*{sT6PQ z6K9S~PLl;vDKbPl*Xl%DBGzGxI9}K4>++CQ`S)EAC*OuXGgbI6iPZ+}yG)z^i;IfT^-$h)@a4xNRumEOLs3%`4SuTC;reE*M1;kpm_v)EoT zGC(9ERF^BQIr=HkpQpys5&#%WMOXd(fkdwXzyTs63dDEQTvrRafGasgoRLI60bBqI0B7saB* z(?|jx8pUHGmp0+Sz7)oUXR*eeh?)Wb?z=*VTfRg@L*qmvD0-`CRX6RpQv4_`sudqM zAx{9fzk6p`pD3k*h`Juoibha<5eIXZFS4qM-$!a5O6jl_z?uf?kfLE!DW(_T&ib6L zC#}TVn;(iV5ei@?F=U7vyV^j60vsolY^PyLe)iDVgoTaDQf)zM%L^h&T2WC zk-}u^m;K3@@0@Y;mIzWyP-`!%-X9DAo=zzcL3~F>0C%lo?>qp8{;o2(#n%9=IvLaf uKtXkxaeUz&Aj7(?`wqvC#L9fL4*w4pTc^#-4=u?60000 Date: Wed, 28 Apr 2021 15:38:50 -0500 Subject: [PATCH 024/178] add value of ecc_thresh x 10 to filename for debug print --- plantcv/plantcv/detect_discs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plantcv/plantcv/detect_discs.py b/plantcv/plantcv/detect_discs.py index f5aac1383..4f8ec9887 100644 --- a/plantcv/plantcv/detect_discs.py +++ b/plantcv/plantcv/detect_discs.py @@ -6,7 +6,7 @@ def detect_discs(bin_img, ecc_thresh=0): """ Detect disc-shaped regions in a binary image based on eccentricity. - A value of eccentricity between 0 and 1 correspond to an ellipse. + A value of eccentricity between 0 and 1 corresponds to an ellipse. The closer the value to 0 the closer the shape is to a circle. Inputs: @@ -41,6 +41,7 @@ def detect_discs(bin_img, ecc_thresh=0): discs_mask = discs_mask + (labeled_img == i+1) _debug(visual=255*discs_mask, filename=os.path.join(params.debug_outdir, - str(params.device) + "_discs_mask.png")) + str(params.device) + "_discs_mask" + + str(int(ecc_thresh*10)) + ".png")) return discs_mask, discs_coor From d764d8a2f63453bd01fe9ac303cfa6804d0370ec Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Wed, 28 Apr 2021 16:03:22 -0500 Subject: [PATCH 025/178] add documentation for get centroids --- docs/get_centroids.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/get_centroids.md diff --git a/docs/get_centroids.md b/docs/get_centroids.md new file mode 100644 index 000000000..ca527ba57 --- /dev/null +++ b/docs/get_centroids.md @@ -0,0 +1,35 @@ +## Get centroids (developing) + +Get the coordinates (row,column) of the centroid of each connected region +in a binary image. + +**plantcv.get_centroids**(*bin_img*) + +**returns** coordinates of centroids + +- **Parameters:** + - bin_img - Binary image containing the connected regions to consider +- **Context:** + - Given an arbitrary mask of the objects of interest, `get_centroids` + returns a list of coordinates that can the be imported by + `plantcv.visualize.ClickCount`. + +- **Example use:** + - Below + +**Binary image** + +![count_im](img/documentation_images/detect_discs/discs_mask_scaled.png) + +```python + +from plantcv import plantcv as pcv + + +# Apply get centroids to the binary image +coordinates = pcv.get_centroids(bin_img=binary_img) +print(coordinates) +#[[1902, 600], [1839, 1363], [1837, 383], [1669, 1977], [1631, 1889], [1590, 1372], [1550, 1525], [1538, 1633], [1522, 1131], [1494, 2396], [1482, 1917], [1446, 1808], [1425, 726], [1418, 2392], [1389, 198], [1358, 1712], [1288, 522], [1289, 406], [1279, 368], [1262, 1376], [1244, 1795], [1224, 1327], [1201, 624], [1181, 725], [1062, 85], [999, 840], [885, 399], [740, 324], [728, 224], [697, 860], [660, 650], [638, 2390], [622, 1565], [577, 497], [572, 2179], [550, 2230], [547, 1826], [537, 892], [538, 481], [524, 2144], [521, 2336], [497, 201], [385, 1141], [342, 683], [342, 102], [332, 1700], [295, 646], [271, 60], [269, 1626], [210, 1694], [189, 878], [178, 1570], [171, 2307], [61, 286], [28, 2342]] + + +``` From d05a98f10ef28aa062ee78735129c0d8dcfe2eae Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Wed, 28 Apr 2021 16:05:23 -0500 Subject: [PATCH 026/178] switch mask type to uint8 (input in get centroids, output in detect discs) --- plantcv/plantcv/detect_discs.py | 2 +- plantcv/plantcv/get_centroids.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plantcv/plantcv/detect_discs.py b/plantcv/plantcv/detect_discs.py index 4f8ec9887..da10cba8e 100644 --- a/plantcv/plantcv/detect_discs.py +++ b/plantcv/plantcv/detect_discs.py @@ -32,7 +32,7 @@ def detect_discs(bin_img, ecc_thresh=0): # Check the eccentricity of each region. # A value closer to 0 keeps only the most circular objects - discs_mask = np.zeros(labeled_img.shape, dtype=np.int32) + discs_mask = np.zeros(labeled_img.shape, dtype=np.uint8) # Store the list of coordinates (row,col) for the objects that pass the discs_coor = [] for i,obj in enumerate(obj_measures): diff --git a/plantcv/plantcv/get_centroids.py b/plantcv/plantcv/get_centroids.py index 2be9b2d0d..5c3b2a9f6 100644 --- a/plantcv/plantcv/get_centroids.py +++ b/plantcv/plantcv/get_centroids.py @@ -1,4 +1,5 @@ import cv2 +import numpy as np def get_centroids(bin_img): """ Get the coordinates (row,column) of the centroid of each connected @@ -16,7 +17,8 @@ def get_centroids(bin_img): """ # find contours in the binary image - _, contours, _ = cv2.findContours(bin_img, cv2.RETR_TREE, + _, contours, _ = cv2.findContours(bin_img.astype(np.uint8), + cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) coor = [] for c in contours: From 0f0cc8fc3ed167b922b1b742e08ed9da090696d8 Mon Sep 17 00:00:00 2001 From: JorgeGtz Date: Wed, 28 Apr 2021 16:07:25 -0500 Subject: [PATCH 027/178] output binary mask as 0-255 instead of 0-1 --- plantcv/plantcv/detect_discs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plantcv/plantcv/detect_discs.py b/plantcv/plantcv/detect_discs.py index da10cba8e..d12eee82c 100644 --- a/plantcv/plantcv/detect_discs.py +++ b/plantcv/plantcv/detect_discs.py @@ -44,4 +44,4 @@ def detect_discs(bin_img, ecc_thresh=0): str(params.device) + "_discs_mask" + str(int(ecc_thresh*10)) + ".png")) - return discs_mask, discs_coor + return 255*discs_mask, discs_coor From 6c0cc09f3376728aeaf0c2ebd5e1147a3d515585 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 17 Sep 2021 10:13:13 -0500 Subject: [PATCH 028/178] Update visualize_click_count.md minor doc updates --- docs/visualize_click_count.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/visualize_click_count.md b/docs/visualize_click_count.md index 75a1b7cff..4a31fb051 100644 --- a/docs/visualize_click_count.md +++ b/docs/visualize_click_count.md @@ -1,4 +1,4 @@ -## Click and count objects (developing) +## Click and count objects `ClickCount` is a class that allows user interactive to count objects in images. @@ -39,7 +39,7 @@ ![ori_im](img/documentation_images/visualize_click_count/count_img.jpg) **Mask of automated detected objects** -Note: for how to get this mask, refer to function `pcv.detect_descs`. +Note: for how to get this mask, refer to function `pcv.detect_discs`. ![count_im](img/documentation_images/visualize_click_count/count_mask.png) From 7d2a653f4ddd612d65bf398dc5cd6669e7b80272 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 17 Sep 2021 10:14:27 -0500 Subject: [PATCH 029/178] remove developing tag --- docs/detect_discs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/detect_discs.md b/docs/detect_discs.md index 6a9085039..622565c3e 100644 --- a/docs/detect_discs.md +++ b/docs/detect_discs.md @@ -1,4 +1,4 @@ -## Detect discs (developing) +## Detect discs Detects disc-shaped regions in a binary image based on eccentricity. A value of eccentricity between 0 and 1 corresponds to an ellipse. From 7c77dea02b481f513f9a5c80c191b7059360fe33 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 17 Sep 2021 10:30:28 -0500 Subject: [PATCH 030/178] Convert coord values to int --- plantcv/plantcv/detect_discs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plantcv/plantcv/detect_discs.py b/plantcv/plantcv/detect_discs.py index d12eee82c..a0a225ddf 100644 --- a/plantcv/plantcv/detect_discs.py +++ b/plantcv/plantcv/detect_discs.py @@ -37,7 +37,8 @@ def detect_discs(bin_img, ecc_thresh=0): discs_coor = [] for i,obj in enumerate(obj_measures): if obj.eccentricity < ecc_thresh: - discs_coor.append(obj.centroid) + # Convert coord values to int + discs_coor.append(tuple(map(int, obj.centroid))) discs_mask = discs_mask + (labeled_img == i+1) _debug(visual=255*discs_mask, filename=os.path.join(params.debug_outdir, From 4782b597dbf9d76ed26e67092b765dfd65872c1e Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 17 Sep 2021 11:06:07 -0500 Subject: [PATCH 031/178] Update detect_discs.py --- plantcv/plantcv/detect_discs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plantcv/plantcv/detect_discs.py b/plantcv/plantcv/detect_discs.py index a0a225ddf..280378181 100644 --- a/plantcv/plantcv/detect_discs.py +++ b/plantcv/plantcv/detect_discs.py @@ -37,8 +37,10 @@ def detect_discs(bin_img, ecc_thresh=0): discs_coor = [] for i,obj in enumerate(obj_measures): if obj.eccentricity < ecc_thresh: - # Convert coord values to int - discs_coor.append(tuple(map(int, obj.centroid))) + # Convert coord values to int + #coords = tuple(map(int, obj.centroid)) + #discs_coor.append(coords) + discs_coor.append(obj.centroid) discs_mask = discs_mask + (labeled_img == i+1) _debug(visual=255*discs_mask, filename=os.path.join(params.debug_outdir, From c29f85f881e969130279e0eb44ead7a46b97c389 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Fri, 17 Sep 2021 13:30:41 -0500 Subject: [PATCH 032/178] remove accidental indent --- tests/tests.py | 1715 +++++++++++++++++++++++++----------------------- 1 file changed, 885 insertions(+), 830 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index 9d23fdaee..01ddeb5bc 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -13,11 +13,14 @@ import plantcv.learn import plantcv.parallel import plantcv.utils +import xarray as xr # Import matplotlib and use a null Template to block plotting to screen # This will let us test debug = "plot" import matplotlib +import matplotlib.pyplot as plt import dask from dask.distributed import Client +from skimage import img_as_ubyte PARALLEL_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parallel_data") TEST_TMPDIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".cache") @@ -383,6 +386,90 @@ def test_plantcv_parallel_metadata_parser_images(): meta = plantcv.parallel.metadata_parser(config=config) assert meta == expected + +def test_plantcv_parallel_metadata_parser_multivalue_filter(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": ["VIS", "NIR"]} + config.imgformat = "jpg" + + meta = plantcv.parallel.metadata_parser(config=config) + expected = { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR, 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': None, + 'id': '117770', + 'plantbarcode': 'none', + 'treatment': 'none', + 'cartag': 'none', + 'measurementlabel': 'none', + 'other': 'none' + }, + 'NIR_SV_0_z1_h1_g0_e65_117779.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR, 'NIR_SV_0_z1_h1_g0_e65_117779.jpg'), + 'camera': 'SV', + 'imgtype': 'NIR', + 'zoom': 'z1', + 'exposure': 'e65', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': None, + 'id': '117779', + 'plantbarcode': 'none', + 'treatment': 'none', + 'cartag': 'none', + 'measurementlabel': 'none', + 'other': 'none' + } + } + assert meta == expected + + +def test_plantcv_parallel_metadata_parser_multivalue_filter_nomatch(): + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR) + config.json = os.path.join(TEST_TMPDIR, "test_plantcv_parallel_metadata_parser_images", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": ["VIS", "PSII"]} + config.imgformat = "jpg" + + meta = plantcv.parallel.metadata_parser(config=config) + expected = { + 'VIS_SV_0_z1_h1_g0_e82_117770.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR, 'VIS_SV_0_z1_h1_g0_e82_117770.jpg'), + 'camera': 'SV', + 'imgtype': 'VIS', + 'zoom': 'z1', + 'exposure': 'e82', + 'gain': 'g0', + 'frame': '0', + 'lifter': 'h1', + 'timestamp': None, + 'id': '117770', + 'plantbarcode': 'none', + 'treatment': 'none', + 'cartag': 'none', + 'measurementlabel': 'none', + 'other': 'none' + } + } + assert meta == expected + + def test_plantcv_parallel_metadata_parser_regex(): # Create config instance config = plantcv.parallel.WorkflowConfig() @@ -455,6 +542,47 @@ def test_plantcv_parallel_metadata_parser_no_default_dates(): assert meta == METADATA_VIS_ONLY +def test_plantcv_parallel_workflowconfig_subdaily_timestampformat(): + ''' + timestampformats with only hours and smaller units of time were failing if the script was run earlier in the day than the images were taken. this was fixed by setting end_date to 23-59-59 if we don't detect the year-month-day + ''' + # Create config instance + config = plantcv.parallel.WorkflowConfig() + config.input_dir = os.path.join(PARALLEL_TEST_DATA, TEST_IMG_DIR2) + config.json = os.path.join( + TEST_IMG_DIR2, "test_plantcv_parallel_metadata_parser_subdaily_timestampformat", "output.json") + config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "timestamp"] + config.workflow = TEST_PIPELINE + config.metadata_filters = {"imgtype": "NIR", "camera": "SV"} + config.start_date = None + config.end_date = None + config.timestampformat = "%H_%M_%S" + config.imgformat = "jpg" + + config.delimiter = r"(NIR)_(SV)_(\d)_(z1)_(h1)_(g0)_(e65)_(\d{2}_\d{2}_\d{2})" + + meta = plantcv.parallel.metadata_parser(config=config) + assert meta == { + 'NIR_SV_0_z1_h1_g0_e65_23_59_59.jpg': { + 'path': os.path.join(PARALLEL_TEST_DATA, 'images_w_date', 'NIR_SV_0_z1_h1_g0_e65_23_59_59.jpg'), + 'imgtype': 'NIR', + 'camera': 'SV', + 'frame': '0', + 'zoom': 'z1', + 'lifter': 'h1', + 'gain': 'g0', + 'exposure': 'e65', + 'timestamp': '23_59_59', + 'measurementlabel': 'none', + 'cartag': 'none', + 'id': 'none', + 'treatment': 'none', + 'plantbarcode': 'none', + 'other': 'none' + } + } + + def test_plantcv_parallel_check_date_range_wrongdateformat(): start_date = 10 end_date = 10 @@ -852,13 +980,18 @@ def test_plantcv_parallel_process_results_invalid_json(): HYPERSPECTRAL_HDR_NO_DEFAULT = "darkReference2.hdr" HYPERSPECTRAL_DATA_APPROX_PSEUDO = "darkReference3" HYPERSPECTRAL_HDR_APPROX_PSEUDO = "darkReference3.hdr" +HYPERSPECTRAL_DATA_BAD_INTERLEAVE = "darkReference4" +HYPERSPECTRAL_HDR_BAD_INTERLEAVE = "darkReference4.hdr" HYPERSPECTRAL_HDR_SMALL_RANGE = {'description': '{[HEADWALL Hyperspec III]}', 'samples': '800', 'lines': '1', 'bands': '978', 'header offset': '0', 'file type': 'ENVI Standard', 'interleave': 'bil', 'sensor type': 'Unknown', 'byte order': '0', 'default bands': '159,253,520', 'wavelength units': 'nm', 'wavelength': ['379.027', '379.663', '380.3', '380.936', '381.573', '382.209']} -FLUOR_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "photosynthesis_data") -FLUOR_IMG = "PSII_PSD_supopt_temp_btx623_22_rep1.DAT" +PHOTOSYNTHESIS_TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "photosynthesis_data") +PHOTOSYNTHESIS_IMG_INF = "PSII_HDR_supopt_temp_btx623_22_rep1.INF" +PHOTOSYNTHESIS_NPQ_IMG_INF = "PSII_HDR_020321_WT_TOP_1.INF" +PHOTOSYNTHESIS_FMASK_SV = "PSII_HDR_supopt_temp_btx623_22_rep1_mask.png" +PHOTOSYNTHESIS_FMASK_TV = "PSII_HDR_020321_WT_TOP_1_mask.png" TEST_COLOR_DIM = (2056, 2454, 3) TEST_GRAY_DIM = (2056, 2454) TEST_BINARY_DIM = TEST_GRAY_DIM @@ -943,8 +1076,53 @@ def test_plantcv_parallel_process_results_invalid_json(): TEST_THERMAL_ARRAY = "thermal_img.npz" TEST_THERMAL_IMG_MASK = "thermal_img_mask.png" TEST_INPUT_THERMAL_CSV = "FLIR2600.csv" +# TEST_BAD_MASK = "bad_mask_test.pkl" +# TEST_IM_BAD_NONE = "bad_mask_none.pkl" +# TEST_IM_BAD_BOTH = "bad_mask_both.pkl" +# TEST_IM_BAD_NAN = "bad_mask_nan.pkl" +# TEST_IM_BAD_INF = "bad_mask_inf.pkl" PIXEL_VALUES = "pixel_inspector_rgb_values.txt" TEST_DISCS_MASK = 'discs_binary.png' +TEST_INPUT_LEAF_MASK = "leaves_mask.png" + +# leaving photosynthesis data here so it can be used to test plot_image and print_image + +def ps_mask(): + """Create simple mask for PSII""" + mask = np.zeros((10, 10), dtype=np.uint8) + mask[5, 5] = 255 + return(mask) + + +def psii_cropreporter(var): + """Create simple data for PSII""" + # sample images + f0 = ps_mask() + f0[5, 5] = 1 + f1 = ps_mask() + f1[5, 5] = 2 + f2 = ps_mask() + f2[5, 5] = 10 + f3 = ps_mask() + f3[5, 5] = 8 + + # set specific labels for xarray for dark and light adapted + if var == 'darkadapted': + frame_labels = ['Fdark', 'F0', 'Fm', '3'] + measurements = ['t0'] + elif var == 'lightadapted': + frame_labels = ['Fdark', 'Fp', '2', 'Fmp'] + measurements = ['t1'] + + # Create DataArray + da = xr.DataArray(data=np.dstack([f0, f1, f2, f3])[..., None], + dims=('x', 'y', 'frame_label', 'measurement'), + coords={'frame_label': frame_labels, + 'frame_num': ('frame_label', [0, 1, 2, 3]), + 'measurement': measurements}, + name=var + ) + return(da) # ########################## @@ -1033,74 +1211,6 @@ def test_plantcv_outputs_save_results_csv(tmpdir): assert results == test_results -def test_plantcv_transform_warp_smaller(): - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) - bimg = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY),-1) - bimg_small = cv2.resize(bimg, (200,300)) #not sure why INTER_NEAREST doesn't preserve values - bimg_small[bimg_small>0]=255 - mrow, mcol = bimg_small.shape - vrow, vcol, vdepth = img.shape - pcv.params.debug = None - mask_warped = pcv.transform.warp(bimg_small, img[:,:,2], - pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], - refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) - pcv.params.debug = 'plot' - mask_warped_plot = pcv.transform.warp(bimg_small, img[:,:,2], - pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], - refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) - - assert np.count_nonzero(mask_warped)==93142 - assert np.count_nonzero(mask_warped_plot)==93142 - - -def test_plantcv_transform_warp_larger(): - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) - gimg = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY),-1) - gimg_large = cv2.resize(gimg, (5000,7000)) - mrow, mcol = gimg_large.shape - vrow, vcol, vdepth = img.shape - pcv.params.debug='print' - mask_warped_print = pcv.transform.warp(gimg_large, img, - pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], - refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) - - assert np.sum(mask_warped_print)==83103814 - - -def test_plantcv_transform_warp_rgbimgerror(): - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) - gimg = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY),-1) - gimg_large = cv2.resize(gimg, (5000,7000)) - mrow, mcol = gimg_large.shape - vrow, vcol, vdepth = img.shape - - with pytest.raises(RuntimeError): - _ = pcv.transform.warp(img, img, - pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], - refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) - - -def test_plantcv_transform_warp_4ptserror(): - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR),-1) - mrow, mcol, _ = img.shape - vrow, vcol, vdepth = img.shape - - with pytest.raises(RuntimeError): - _ = pcv.transform.warp(img[:,:,0], img, - pts = [(0,0),(mcol-1,0),(0,mrow-1)], - refpts = [(0,0),(vcol-1,0),(0,vrow-1)]) - - with pytest.raises(RuntimeError): - _ = pcv.transform.warp(img[:,:,1], img, - pts = [(0,0),(mcol-1,0),(0,mrow-1)], - refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1)]) - - with pytest.raises(RuntimeError): - _ = pcv.transform.warp(img[:,:,2], img, - pts = [(0,0),(mcol-1,0),(mcol-1,mrow-1),(0,mrow-1)], - refpts = [(0,0),(vcol-1,0),(vcol-1,vrow-1),(0,vrow-1),(0,vrow-1)]) - - def test_plantcv_acute(): # Read in test data mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) @@ -1122,22 +1232,10 @@ def test_plantcv_acute(): def test_plantcv_acute_vertex(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_acute_vertex") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL)) contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") obj_contour = contours_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img, label="prefix") - _ = pcv.acute_vertex(obj=[], win=5, thresh=15, sep=5, img=img) - _ = pcv.acute_vertex(obj=[], win=.01, thresh=.01, sep=1, img=img) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img) # Test with debug = None pcv.params.debug = None acute = pcv.acute_vertex(obj=obj_contour, win=5, thresh=15, sep=5, img=img) @@ -1157,27 +1255,17 @@ def test_plantcv_acute_vertex_bad_obj(): def test_plantcv_analyze_bound_horizontal(): # Clear previous outputs pcv.outputs.clear() - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_horizontal") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) img_above_bound_only = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") object_contours = contours_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" + pcv.params.debug = None _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=300, label="prefix") pcv.outputs.clear() _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=100) _ = pcv.analyze_bound_horizontal(img=img_above_bound_only, obj=object_contours, mask=mask, line_position=1756) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756) - # Test with debug = None - pcv.params.debug = None _ = pcv.analyze_bound_horizontal(img=img, obj=object_contours, mask=mask, line_position=1756) assert len(pcv.outputs.observations["default"]) == 7 @@ -1217,21 +1305,11 @@ def test_plantcv_analyze_bound_horizontal_neg_y(): def test_plantcv_analyze_bound_vertical(): # Clear previous outputs pcv.outputs.clear() - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_bound_vertical") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") object_contours = contours_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000, label="prefix") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000) # Test with debug = None pcv.params.debug = None _ = pcv.analyze_bound_vertical(img=img, obj=object_contours, mask=mask, line_position=1000) @@ -1323,13 +1401,14 @@ def test_plantcv_analyze_color(): _ = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type='rgb') assert pcv.outputs.observations['default']['hue_median']['value'] == 84.0 + def test_plantcv_analyze_color_incorrect_image(): img_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) with pytest.raises(RuntimeError): _ = pcv.analyze_color(rgb_img=img_binary, mask=mask, hist_plot_type=None) -# -# + + def test_plantcv_analyze_color_bad_hist_type(): img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) @@ -1484,6 +1563,7 @@ def test_plantcv_analyze_thermal_values(): thermal_hist = pcv.analyze_thermal_values(thermal_array=img, mask=mask, histplot=True) assert thermal_hist is not None and pcv.outputs.observations['default']['median_temp']['value'] == 33.20922 + def test_plantcv_apply_mask_white(): # Test cache directory cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_apply_mask_white") @@ -1492,12 +1572,6 @@ def test_plantcv_apply_mask_white(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.apply_mask(img=img, mask=mask, mask_color="white") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.apply_mask(img=img, mask=mask, mask_color="white") # Test with debug = None pcv.params.debug = None masked_img = pcv.apply_mask(img=img, mask=mask, mask_color="white") @@ -1512,12 +1586,6 @@ def test_plantcv_apply_mask_black(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.apply_mask(img=img, mask=mask, mask_color="black") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.apply_mask(img=img, mask=mask, mask_color="black") # Test with debug = None pcv.params.debug = None masked_img = pcv.apply_mask(img=img, mask=mask, mask_color="black") @@ -1535,11 +1603,8 @@ def test_plantcv_apply_mask_hyperspectral(): img = np.ones((2056, 2454)) img_stacked = cv2.merge((img, img, img, img)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.apply_mask(img=img_stacked, mask=img, mask_color="black") - # Test with debug = "plot" - pcv.params.debug = "plot" + # Test with debug = none + pcv.params.debug = None masked_array = pcv.apply_mask(img=hyper_array.array_data, mask=img, mask_color="black") assert np.mean(masked_array) == 13.97111260224949 @@ -1554,24 +1619,18 @@ def test_plantcv_apply_mask_bad_input(): def test_plantcv_auto_crop(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_crop") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI), -1) contours = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_OBJECT), encoding="latin1") roi_contours = [contours[arr_n] for arr_n in contours] - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=(20, 10), padding_y=(20, 10), color='black') - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.auto_crop(img=img1, obj=roi_contours[1], color='image') - _ = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=2000, padding_y=2000, color='image') # Test with debug = None pcv.params.debug = None - cropped = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=20, padding_y=20, color='black') + # padding as tuple + _ = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=(20, 10), padding_y=(20, 10), color='black') + # padding 0 so crop same as image + _ = pcv.auto_crop(img=img1, obj=roi_contours[1], color='image') + # padding as int + cropped = pcv.auto_crop(img=img1, obj=roi_contours[1], padding_x=20, padding_y=20, color='image') x, y, z = np.shape(img1) x1, y1, z1 = np.shape(cropped) assert x > x1 @@ -1624,14 +1683,9 @@ def test_plantcv_canny_edge_detect(): rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" _ = pcv.canny_edge_detect(img=rgb_img, mask=mask, mask_color='white') _ = pcv.canny_edge_detect(img=img, mask=mask, mask_color='black') - # Test with debug = "plot" - pcv.params.debug = "plot" _ = pcv.canny_edge_detect(img=img, thickness=2) - _ = pcv.canny_edge_detect(img=img) # Test with debug = None pcv.params.debug = None edge_img = pcv.canny_edge_detect(img=img) @@ -1666,12 +1720,7 @@ def test_plantcv_closing(): bin_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) # Test with debug=None pcv.params.debug = None - _ = pcv.closing(gray_img) - # Test with debug='plot' - pcv.params.debug = 'plot' - _ = pcv.closing(bin_img, np.ones((4, 4), np.uint8)) - # Test with debug='print' - pcv.params.debug = 'print' + _ = pcv.closing(gray_img, np.ones((4, 4), np.uint8)) filtered_img = pcv.closing(bin_img) assert np.sum(filtered_img) == 16261860 @@ -1694,13 +1743,9 @@ def test_plantcv_cluster_contours(): hierarchy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1") objs = [roi_objects[arr_n] for arr_n in roi_objects] obj_hierarchy = hierarchy['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) + # Test with debug = 'plot' to cover plotting logic + pcv.params.debug = 'plot' _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, show_grid=True) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) # Test with debug = None pcv.params.debug = None clusters_i, contours, hierarchy = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, @@ -1721,12 +1766,9 @@ def test_plantcv_cluster_contours_grayscale_input(): hierachy = np.load(os.path.join(TEST_DATA, TEST_INPUT_MULTI_HIERARCHY), encoding="latin1") objs = [roi_objects[arr_n] for arr_n in roi_objects] obj_hierarchy = hierachy['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, nrow=4, ncol=6) + # Test with debug = 'plot' to cover plotting logic + pcv.params.debug = 'plot' + _ = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, show_grid=True) # Test with debug = None pcv.params.debug = None clusters_i, contours, hierachy = pcv.cluster_contours(img=img1, roi_objects=objs, roi_obj_hierarchy=obj_hierarchy, @@ -1824,11 +1866,7 @@ def test_plantcv_crop(): os.mkdir(cache_dir) pcv.params.debug_outdir = cache_dir img, _, _ = pcv.readimage(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), 'gray') - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.crop(img=img, x=10, y=10, h=50, w=50) - # Test with debug = "plot" - pcv.params.debug = "plot" + pcv.params.debug = None cropped = pcv.crop(img=img, x=10, y=10, h=50, w=50) assert np.shape(cropped) == (50, 50) @@ -1841,11 +1879,7 @@ def test_plantcv_crop_hyperspectral(): # Read in test data img = np.ones((2056, 2454)) img_stacked = cv2.merge((img, img, img, img)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.crop(img=img_stacked, x=10, y=10, h=50, w=50) - # Test with debug = "plot" - pcv.params.debug = "plot" + pcv.params.debug = None cropped = pcv.crop(img=img_stacked, x=10, y=10, h=50, w=50) assert np.shape(cropped) == (50, 50, 4) @@ -1955,12 +1989,6 @@ def test_plantcv_dilate(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.dilate(gray_img=img, ksize=5, i=1) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.dilate(gray_img=img, ksize=5, i=1) # Test with debug = None pcv.params.debug = None dilate_img = pcv.dilate(gray_img=img, ksize=5, i=1) @@ -1991,12 +2019,6 @@ def test_plantcv_erode(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.erode(gray_img=img, ksize=5, i=1) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.erode(gray_img=img, ksize=5, i=1) # Test with debug = None pcv.params.debug = None erode_img = pcv.erode(gray_img=img, ksize=5, i=1) @@ -2027,12 +2049,6 @@ def test_plantcv_distance_transform(): pcv.params.debug_outdir = cache_dir # Read in test data mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED_MASK), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3) # Test with debug = None pcv.params.debug = None distance_transform_img = pcv.distance_transform(bin_img=mask, distance_type=1, mask_size=3) @@ -2047,18 +2063,8 @@ def test_plantcv_fatal_error(): def test_plantcv_fill(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_fill") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.fill(bin_img=img, size=63632) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.fill(bin_img=img, size=63632) # Test with debug = None pcv.params.debug = None fill_img = pcv.fill(bin_img=img, size=63632) @@ -2085,11 +2091,6 @@ def test_plantcv_fill_holes(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.fill_holes(bin_img=img) - pcv.params.debug = "plot" - _ = pcv.fill_holes(bin_img=img) # Test with debug = None pcv.params.debug = None fill_img = pcv.fill_holes(bin_img=img) @@ -2115,12 +2116,6 @@ def test_plantcv_find_objects(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.find_objects(img=img, mask=mask) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.find_objects(img=img, mask=mask) # Test with debug = None pcv.params.debug = None contours, hierarchy = pcv.find_objects(img=img, mask=mask) @@ -2136,8 +2131,8 @@ def test_plantcv_find_objects_grayscale_input(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 0) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "plot" - pcv.params.debug = "plot" + # Test with debug = None + pcv.params.debug = None contours, hierarchy = pcv.find_objects(img=img, mask=mask) # Assert the correct number of contours are found assert len(contours) == 2 @@ -2151,15 +2146,9 @@ def test_plantcv_flip(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) img_binary = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.flip(img=img, direction="horizontal") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.flip(img=img, direction="vertical") - _ = pcv.flip(img=img_binary, direction="vertical") # Test with debug = None pcv.params.debug = None + _ = pcv.flip(img=img_binary, direction="vertical") flipped_img = pcv.flip(img=img, direction="horizontal") assert all([i == j] for i, j in zip(np.shape(flipped_img), TEST_COLOR_DIM)) @@ -2179,15 +2168,9 @@ def test_plantcv_gaussian_blur(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None) - _ = pcv.gaussian_blur(img=img_color, ksize=(51, 51), sigma_x=0, sigma_y=None) # Test with debug = None pcv.params.debug = None + _ = pcv.gaussian_blur(img=img_color, ksize=(51, 51), sigma_x=0, sigma_y=None) gaussian_img = pcv.gaussian_blur(img=img, ksize=(51, 51), sigma_x=0, sigma_y=None) imgavg = np.average(img) gavg = np.average(gaussian_img) @@ -2238,12 +2221,6 @@ def test_plantcv_hist_equalization(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.hist_equalization(gray_img=img) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.hist_equalization(gray_img=img) # Test with debug = None pcv.params.debug = None hist = pcv.hist_equalization(gray_img=img) @@ -2258,7 +2235,7 @@ def test_plantcv_hist_equalization_bad_input(): os.mkdir(cache_dir) pcv.params.debug_outdir = cache_dir # Read in test data - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), 1) + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR), 1) # Test with debug = None pcv.params.debug = None with pytest.raises(RuntimeError): @@ -2273,18 +2250,31 @@ def test_plantcv_image_add(): # Read in test data img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) img2 = np.copy(img1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.image_add(gray_img1=img1, gray_img2=img2) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.image_add(gray_img1=img1, gray_img2=img2) # Test with debug = None pcv.params.debug = None added_img = pcv.image_add(gray_img1=img1, gray_img2=img2) assert all([i == j] for i, j in zip(np.shape(added_img), TEST_BINARY_DIM)) +def test_plantcv_image_fusion(): + # Read in test data + # 16-bit image + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) + img2 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN)) + # 8-bit image + img2 = img_as_ubyte(img2) + fused_img = pcv.image_fusion(img1, img2, [480.0], [550.0, 640.0, 800.0]) + assert str(type(fused_img)) == "" + + +def test_plantcv_image_fusion_size_diff(): + img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), 0) + img2 = np.copy(img1) + img2 = img2[0:10, 0:10] + with pytest.raises(RuntimeError): + _ = pcv.image_fusion(img1, img2, [480.0, 550.0, 670.0], [480.0, 550.0, 670.0]) + + def test_plantcv_image_subtract(): # Test cache directory cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_image_sub") @@ -2293,12 +2283,6 @@ def test_plantcv_image_subtract(): # read in images img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) img2 = np.copy(img1) - # Test with debug = "print" - pcv.params.debug = 'print' - _ = pcv.image_subtract(img1, img2) - # Test with debug = "plot" - pcv.params.debug = 'plot' - _ = pcv.image_subtract(img1, img2) # Test with debug = None pcv.params.debug = None new_img = pcv.image_subtract(img1, img2) @@ -2321,12 +2305,6 @@ def test_plantcv_invert(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.invert(gray_img=img) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.invert(gray_img=img) # Test with debug = None pcv.params.debug = None inverted_img = pcv.invert(gray_img=img) @@ -2368,12 +2346,6 @@ def test_plantcv_laplace_filter(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.laplace_filter(gray_img=img, ksize=1, scale=1) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.laplace_filter(gray_img=img, ksize=1, scale=1) # Test with debug = None pcv.params.debug = None lp_img = pcv.laplace_filter(gray_img=img, ksize=1, scale=1) @@ -2389,12 +2361,6 @@ def test_plantcv_logical_and(): # Read in test data img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) img2 = np.copy(img1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.logical_and(bin_img1=img1, bin_img2=img2) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.logical_and(bin_img1=img1, bin_img2=img2) # Test with debug = None pcv.params.debug = None and_img = pcv.logical_and(bin_img1=img1, bin_img2=img2) @@ -2409,12 +2375,6 @@ def test_plantcv_logical_or(): # Read in test data img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) img2 = np.copy(img1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.logical_or(bin_img1=img1, bin_img2=img2) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.logical_or(bin_img1=img1, bin_img2=img2) # Test with debug = None pcv.params.debug = None or_img = pcv.logical_or(bin_img1=img1, bin_img2=img2) @@ -2429,12 +2389,6 @@ def test_plantcv_logical_xor(): # Read in test data img1 = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) img2 = np.copy(img1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.logical_xor(bin_img1=img1, bin_img2=img2) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.logical_xor(bin_img1=img1, bin_img2=img2) # Test with debug = None pcv.params.debug = None xor_img = pcv.logical_xor(bin_img1=img1, bin_img2=img2) @@ -2448,14 +2402,9 @@ def test_plantcv_median_blur(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.median_blur(gray_img=img, ksize=5) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.median_blur(gray_img=img, ksize=5) # Test with debug = None pcv.params.debug = None + _ = pcv.median_blur(gray_img=img, ksize=(5, 5)) blur_img = pcv.median_blur(gray_img=img, ksize=5) # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(blur_img), TEST_BINARY_DIM)): @@ -2486,9 +2435,6 @@ def test_plantcv_naive_bayes_classifier(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) # Test with debug = "plot" pcv.params.debug = "plot" _ = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) @@ -2526,15 +2472,9 @@ def test_plantcv_object_composition(): object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") object_hierarchy = object_hierarchy_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy) - _ = pcv.object_composition(img=img, contours=[], hierarchy=object_hierarchy) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy) # Test with debug = None pcv.params.debug = None + _ = pcv.object_composition(img=img, contours=[], hierarchy=object_hierarchy) contours, mask = pcv.object_composition(img=img, contours=object_contours, hierarchy=object_hierarchy) # Assert that the objects have been combined contour_shape = np.shape(contours) # type: tuple @@ -2595,11 +2535,7 @@ def test_plantcv_opening(): # Test with debug=None pcv.params.debug = None _ = pcv.opening(gray_img) - # Test with debug='plot' - pcv.params.debug = 'plot' _ = pcv.opening(bin_img, np.ones((4, 4), np.uint8)) - # Test with debug='print' - pcv.params.debug = 'print' filtered_img = pcv.opening(bin_img) assert np.sum(filtered_img) == 16184595 @@ -2683,6 +2619,70 @@ def test_plantcv_plot_image_plotnine(): assert True +@pytest.mark.parametrize(['da', 'kwarg'], + [ + [psii_cropreporter('darkadapted').squeeze('measurement', drop=True), 'frame_label'] + ]) +def test_plantcv_show_dataarray(da, kwarg): + from plantcv.plantcv._show_dataarray import _show_dataarray + try: + _show_dataarray(da, col=kwarg) + except RuntimeError: + assert False + # Assert that the image was plotted without error + assert True + + +@pytest.mark.parametrize(['da', 'kwarg'], + [ + # missing col or row as kwarg + [psii_cropreporter('darkadapted').squeeze('measurement', drop=True), None], + # missing x or y dim + [psii_cropreporter('darkadapted').squeeze('measurement', drop=True)[0, :, :], 'frame_label'] + ] + ) +def test_plantcv_show_dataarray_fatal_error(da, kwarg): + from plantcv.plantcv._show_dataarray import _show_dataarray + with pytest.raises(RuntimeError): + _show_dataarray(da, col=kwarg) + +@pytest.mark.parametrize(['da'], + [ + #too many dims + [psii_cropreporter('darkadapted')], + # not enough dims + [psii_cropreporter('darkadapted')[:, :, 0, 0]] + ]) +def test_plantcv_show_dataarray_value_error(da): + from plantcv.plantcv._show_dataarray import _show_dataarray + # pcolormesh() fails with ValueError if ndim != 2 in addition to row and/or col + with pytest.raises(ValueError): + _show_dataarray(img=da, col_wrap=4, row='frame_label') + + +@pytest.mark.parametrize(['da', 'kwarg'], + [[psii_cropreporter('darkadapted').squeeze('measurement', drop=True), 'frame_label']] + ) +def test_plantcv_plot_image_dataarray(da, kwarg): + try: + pcv.plot_image(da, col=kwarg) + except RuntimeError: + assert False + # Assert that the image was plotted without error + assert True + + +def test_plantcv_plot_image_psiidata(): + psii = pcv.PSII_data() + with pytest.raises(RuntimeError): + pcv.plot_image(psii) + + +def test_plantcv_plot_image_bad_type(): + with pytest.raises(RuntimeError): + pcv.plot_image(img=[], filename="/dev/null") + + def test_plantcv_print_image(): # Test cache directory cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_print_image") @@ -2713,6 +2713,37 @@ def test_plantcv_print_image_plotnine(): assert os.path.exists(filename) is True +def test_plantcv_print_image_matplotlib(): + # Test cache directory + cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_print_image_plotnine") + os.mkdir(cache_dir) + # Input data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + plt.figure() + plt.imshow(img) + plot = plt.gcf() + filename = os.path.join(cache_dir, 'plantcv_print_image.png') + pcv.print_image(img=plot, filename=filename) + # Assert that the file was created + assert os.path.exists(filename) is True + + +def test_plantcv_print_image_dataarray(): + da = psii_cropreporter('darkadapted').squeeze('measurement', drop=True) + try: + pcv.print_image(img=da, col='frame_label', filename='/dev/null') + except RuntimeError: + assert False + # Assert that the image was plotted without error + assert True + + +def test_plantcv_print_image_psiidata(): + psii = pcv.PSII_data() + with pytest.raises(RuntimeError): + pcv.print_image(img=psii, filename='/dev/null') + + def test_plantcv_print_results(tmpdir): # Create a tmp directory cache_dir = tmpdir.mkdir("sub") @@ -2781,107 +2812,27 @@ def test_plantcv_readimage_bad_file(): _ = pcv.readimage(filename=TEST_INPUT_COLOR) -def test_plantcv_readbayer_default_bg(): +@pytest.mark.parametrize("alg, pattern", [["default", 'BG'], + ["default", 'GB'], + ["default", 'RG'], + ["default", 'GR'], + ["edgeaware", 'BG'], + ["edgeaware", 'GB'], + ["edgeaware", 'RG'], + ["edgeaware", 'GR'], + ["variablenumbergradients", 'BG'], + ["variablenumbergradients", 'GB'], + ["variablenumbergradients", 'RG'], + ["variablenumbergradients", 'GR']]) +def test_plantcv_readbayer(alg, pattern): # Test cache directory cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_readbayer_default_bg") os.mkdir(cache_dir) pcv.params.debug_outdir = cache_dir - # Test with debug = "print" - pcv.params.debug = "print" - _, _, _ = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="BG", alg="default") - # Test with debug = "plot" - pcv.params.debug = "plot" - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="BG", alg="default") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_default_gb(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="GB", alg="default") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_default_rg(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="RG", alg="default") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_default_gr(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="GR", alg="default") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_edgeaware_bg(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="BG", alg="edgeaware") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_edgeaware_gb(): # Test with debug = None pcv.params.debug = None img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="GB", alg="edgeaware") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_edgeaware_rg(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="RG", alg="edgeaware") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_edgeaware_gr(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="GR", alg="edgeaware") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_variablenumbergradients_bg(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="BG", alg="variablenumbergradients") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_variablenumbergradients_gb(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="GB", alg="variablenumbergradients") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_variablenumbergradients_rg(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="RG", alg="variablenumbergradients") - assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) - - -def test_plantcv_readbayer_variablenumbergradients_gr(): - # Test with debug = None - pcv.params.debug = None - img, path, img_name = pcv.readbayer(filename=os.path.join(TEST_DATA, TEST_INPUT_BAYER), - bayerpattern="GR", alg="variablenumbergradients") + bayerpattern=pattern, alg=alg) assert all([i == j] for i, j in zip(np.shape(img), (335, 400, 3))) @@ -2900,15 +2851,10 @@ def test_plantcv_rectangle_mask(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) img_color = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="white") - _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="white") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.rectangle_mask(img=img_color, p1=(0, 0), p2=(2454, 2056), color="gray") # Test with debug = None pcv.params.debug = None + _ = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="white") + _ = pcv.rectangle_mask(img=img_color, p1=(0, 0), p2=(2454, 2056), color="gray") masked, hist, contour, heir = pcv.rectangle_mask(img=img, p1=(0, 0), p2=(2454, 2056), color="black") maskedsum = np.sum(masked) imgsum = np.sum(img) @@ -2939,14 +2885,6 @@ def test_plantcv_report_size_marker_detect(): roi_contour = [np.array([[[3550, 850]], [[3550, 1349]], [[4049, 1349]], [[4049, 850]]], dtype=np.int32)] roi_hierarchy = np.array([[[-1, -1, -1, -1]]], dtype=np.int32) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect', - objcolor='light', thresh_channel='s', thresh=120, label="prefix") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect', - objcolor='light', thresh_channel='s', thresh=120) # Test with debug = None pcv.params.debug = None images = pcv.report_size_marker_area(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, marker='detect', @@ -3032,12 +2970,6 @@ def test_plantcv_rgb2gray_hsv(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.rgb2gray_hsv(rgb_img=img, channel="s") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.rgb2gray_hsv(rgb_img=img, channel="s") # Test with debug = None pcv.params.debug = None s = pcv.rgb2gray_hsv(rgb_img=img, channel="s") @@ -3059,12 +2991,6 @@ def test_plantcv_rgb2gray_lab(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.rgb2gray_lab(rgb_img=img, channel='b') - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.rgb2gray_lab(rgb_img=img, channel='b') # Test with debug = None pcv.params.debug = None b = pcv.rgb2gray_lab(rgb_img=img, channel='b') @@ -3086,12 +3012,6 @@ def test_plantcv_rgb2gray(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.rgb2gray(rgb_img=img) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.rgb2gray(rgb_img=img) # Test with debug = None pcv.params.debug = None gray = pcv.rgb2gray(rgb_img=img) @@ -3108,9 +3028,7 @@ def test_plantcv_roi2mask(): img = cv2.imread(os.path.join(TEST_DATA, TEST_VIS_SMALL)) contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") obj_contour = contours_npz['arr_0'] - pcv.params.debug = "plot" - _ = pcv.roi.roi2mask(img=img, contour=obj_contour) - pcv.params.debug = "print" + pcv.params.debug = None mask = pcv.roi.roi2mask(img=img, contour=obj_contour) assert np.shape(mask)[0:2] == np.shape(img)[0:2] and np.sum(mask) == 255 @@ -3130,16 +3048,10 @@ def test_plantcv_roi_objects(): object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") object_hierarchy = object_hierarchy_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" + # Test with debug = None + pcv.params.debug = None _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="largest") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, - object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="partial") - # Test with debug = None and roi_type = cutto - pcv.params.debug = None _ = pcv.roi_objects(img=img, roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, object_contour=object_contours, obj_hierarchy=object_hierarchy, roi_type="cutto") # Test with debug = None @@ -3183,8 +3095,7 @@ def test_plantcv_roi_objects_grayscale_input(): object_contours = [object_contours_npz[arr_n] for arr_n in object_contours_npz] object_hierarchy_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_OBJECT_HIERARCHY), encoding="latin1") object_hierarchy = object_hierarchy_npz['arr_0'] - # Test with debug = "plot" - pcv.params.debug = "plot" + pcv.params.debug = None kept_contours, kept_hierarchy, mask, area = pcv.roi_objects(img=img, roi_type="partial", roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, object_contour=object_contours, @@ -3192,6 +3103,7 @@ def test_plantcv_roi_objects_grayscale_input(): # Assert that the contours were filtered as expected assert len(kept_contours) == 1891 + def test_plantcv_rotate(): img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) rotated = pcv.rotate(img=img, rotation_deg=45, crop=True) @@ -3199,6 +3111,7 @@ def test_plantcv_rotate(): rotateavg = np.average(rotated) assert rotateavg != imgavg + def test_plantcv_transform_rotate(): # Test cache directory cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_rotate_img") @@ -3242,11 +3155,8 @@ def test_plantcv_scale_features(): mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") obj_contour = contours_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position=50) - # Test with debug = "plot" - pcv.params.debug = "plot" + # test with debug = 'plot' to cover plotting logic + pcv.params.debug = 'plot' _ = pcv.scale_features(obj=obj_contour, mask=mask, points=TEST_ACUTE_RESULT, line_position='NA') # Test with debug = None pcv.params.debug = None @@ -3271,12 +3181,6 @@ def test_plantcv_scharr_filter(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) - pcv.params.debug = "print" - # Test with debug = "print" - _ = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1) # Test with debug = None pcv.params.debug = None scharr_img = pcv.scharr_filter(img=img, dx=1, dy=0, scale=1) @@ -3292,20 +3196,10 @@ def test_plantcv_shift_img(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.shift_img(img=img, number=300, side="top") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.shift_img(img=img, number=300, side="top") - # Test with debug = "plot" + pcv.params.debug = None _ = pcv.shift_img(img=img, number=300, side="bottom") - # Test with debug = "plot" _ = pcv.shift_img(img=img, number=300, side="right") - # Test with debug = "plot" _ = pcv.shift_img(img=mask, number=300, side="left") - # Test with debug = None - pcv.params.debug = None rotated = pcv.shift_img(img=img, number=300, side="top") imgavg = np.average(img) shiftavg = np.average(rotated) @@ -3335,12 +3229,6 @@ def test_plantcv_sobel_filter(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1) # Test with debug = None pcv.params.debug = None sobel_img = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1) @@ -3355,9 +3243,7 @@ def test_plantcv_stdev_filter(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) - pcv.params.debug = "plot" - _ = pcv.stdev_filter(img=img, ksize=11) - pcv.params.debug = "print" + pcv.params.debug = None filter_img = pcv.stdev_filter(img=img, ksize=11) assert (np.shape(filter_img) == np.shape(img)) @@ -3372,16 +3258,10 @@ def test_plantcv_watershed_segmentation(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED)) mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_CROPPED_MASK), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10, label="prefix") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10) # Test with debug = None pcv.params.debug = None - _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10) - assert pcv.outputs.observations['default']['estimated_object_count']['value'] > 9 + _ = pcv.watershed_segmentation(rgb_img=img, mask=mask, distance=10, label='prefix') + assert pcv.outputs.observations['prefix']['estimated_object_count']['value'] > 9 def test_plantcv_white_balance_gray_16bit(): @@ -3391,17 +3271,11 @@ def test_plantcv_white_balance_gray_16bit(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80)) # Test without an ROI pcv.params.debug = None - _ = pcv.white_balance(img=img, mode='hist', roi=None) + _ = pcv.white_balance(img=img, mode='max', roi=None) # Test with debug = None - white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80)) + white_balanced = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) imgavg = np.average(img) balancedavg = np.average(white_balanced) assert balancedavg != imgavg @@ -3415,17 +3289,11 @@ def test_plantcv_white_balance_gray_8bit(): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK)) img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80)) # Test without an ROI pcv.params.debug = None - _ = pcv.white_balance(img=img, mode='hist', roi=None) + _ = pcv.white_balance(img=img, mode='max', roi=None) # Test with debug = None - white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80)) + white_balanced = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) imgavg = np.average(img) balancedavg = np.average(white_balanced) assert balancedavg != imgavg @@ -3438,47 +3306,26 @@ def test_plantcv_white_balance_rgb(): pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.white_balance(img=img, mode='max', roi=(5, 5, 80, 80)) - # Test without an ROI pcv.params.debug = None - _ = pcv.white_balance(img=img, mode='hist', roi=None) + # Test without an ROI + _ = pcv.white_balance(img=img, mode='max', roi=None) # Test with debug = None - white_balanced = pcv.white_balance(img=img, roi=(5, 5, 80, 80)) + white_balanced = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 80, 80)) imgavg = np.average(img) balancedavg = np.average(white_balanced) assert balancedavg != imgavg -def test_plantcv_white_balance_bad_input(): - # Read in test data - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1) - # Test with debug = None - with pytest.raises(RuntimeError): - pcv.params.debug = "plot" - _ = pcv.white_balance(img=img, mode='hist', roi=(5, 5, 5, 5, 5)) - - -def test_plantcv_white_balance_bad_mode_input(): - # Read in test data - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER)) - # Test with debug = None - with pytest.raises(RuntimeError): - pcv.params.debug = "plot" - _ = pcv.white_balance(img=img, mode='histogram', roi=(5, 5, 80, 80)) - - -def test_plantcv_white_balance_bad_input_int(): +@pytest.mark.parametrize("mode, roi", [['hist', (5, 5, 5, 5, 5)], # too many points + ['hist', (5., 5, 5, 5)], # not all integers + ['histogram', (5, 5, 80, 80)]]) # bad mode +def test_plantcv_white_balance_bad_input(mode, roi): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_NIR_MASK), -1) # Test with debug = None with pytest.raises(RuntimeError): - pcv.params.debug = "plot" - _ = pcv.white_balance(img=img, mode='hist', roi=(5., 5, 5, 5)) + pcv.params.debug = None + _ = pcv.white_balance(img=img, mode=mode, roi=roi) def test_plantcv_x_axis_pseudolandmarks(): @@ -3490,18 +3337,14 @@ def test_plantcv_x_axis_pseudolandmarks(): mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") obj_contour = contours_npz['arr_0'] - pcv.params.debug = "print" - _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) - # Test with debug = "plot" - pcv.params.debug = "plot" + # Test with debug = None + pcv.params.debug = None _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img, label="prefix") _ = pcv.x_axis_pseudolandmarks(obj=np.array([[0, 0], [0, 0]]), mask=np.array([[0, 0], [0, 0]]), img=img) _ = pcv.x_axis_pseudolandmarks(obj=np.array(([[89, 222]], [[252, 39]], [[89, 207]])), mask=np.array(([[42, 161]], [[2, 47]], [[211, 222]])), img=img) _ = pcv.x_axis_pseudolandmarks(obj=(), mask=mask, img=img) - # Test with debug = None - pcv.params.debug = None top, bottom, center_v = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) pcv.outputs.clear() assert all([all([i == j] for i, j in zip(np.shape(top), (20, 1, 2))), @@ -3514,12 +3357,9 @@ def test_plantcv_x_axis_pseudolandmarks_small_obj(): mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT), -1) contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR_SMALL_PLANT), encoding="latin1") obj_contour = contours_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" - _, _, _ = pcv.x_axis_pseudolandmarks(obj=[], mask=mask, img=img) + # Test with debug = None + pcv.params.debug = None _, _, _ = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) - # Test with debug = "plot" - pcv.params.debug = "plot" _, _, _ = pcv.x_axis_pseudolandmarks(obj=[], mask=mask, img=img) top, bottom, center_v = pcv.x_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) assert all([all([i == j] for i, j in zip(np.shape(top), (20, 1, 2))), @@ -3551,20 +3391,14 @@ def test_plantcv_y_axis_pseudolandmarks(): mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL), -1) contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR), encoding="latin1") obj_contour = contours_npz['arr_0'] - pcv.params.debug = "print" - _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img, label="prefix") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) - pcv.outputs.clear() + # Test with debug = None + pcv.params.debug = None _ = pcv.y_axis_pseudolandmarks(obj=[], mask=mask, img=img) _ = pcv.y_axis_pseudolandmarks(obj=(), mask=mask, img=img) _ = pcv.y_axis_pseudolandmarks(obj=np.array(([[89, 222]], [[252, 39]], [[89, 207]])), mask=np.array(([[42, 161]], [[2, 47]], [[211, 222]])), img=img) _ = pcv.y_axis_pseudolandmarks(obj=np.array(([[21, 11]], [[159, 155]], [[237, 11]])), mask=np.array(([[38, 54]], [[144, 169]], [[81, 137]])), img=img) - # Test with debug = None - pcv.params.debug = None left, right, center_h = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) pcv.outputs.clear() assert all([all([i == j] for i, j in zip(np.shape(left), (20, 1, 2))), @@ -3579,12 +3413,8 @@ def test_plantcv_y_axis_pseudolandmarks_small_obj(): mask = cv2.imread(os.path.join(TEST_DATA, TEST_MASK_SMALL_PLANT), -1) contours_npz = np.load(os.path.join(TEST_DATA, TEST_VIS_COMP_CONTOUR_SMALL_PLANT), encoding="latin1") obj_contour = contours_npz['arr_0'] - # Test with debug = "print" - pcv.params.debug = "print" + pcv.params.debug = None _, _, _ = pcv.y_axis_pseudolandmarks(obj=[], mask=mask, img=img) - _, _, _ = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) - # Test with debug = "plot" - pcv.params.debug = "plot" pcv.outputs.clear() left, right, center_h = pcv.y_axis_pseudolandmarks(obj=obj_contour, mask=mask, img=img) pcv.outputs.clear() @@ -3634,27 +3464,6 @@ def test_plantcv_background_subtraction(): assert (all(truths)) -def test_plantcv_background_subtraction_debug(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_background_subtraction_debug") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir - # List to hold result of all tests. - truths = [] - fg_img = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND)) - bg_img = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND)) - # Test with debug = "print" - pcv.params.debug = "print" - fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img) - truths.append(np.sum(fgmask) > 0) - # Test with debug = "plot" - pcv.params.debug = "plot" - fgmask = pcv.background_subtraction(background_image=bg_img, foreground_image=fg_img) - truths.append(np.sum(fgmask) > 0) - # All of these should be true for the function to pass testing. - assert (all(truths)) - - def test_plantcv_background_subtraction_bad_img_type(): fg_color = cv2.imread(os.path.join(TEST_DATA, TEST_FOREGROUND)) bg_gray = cv2.imread(os.path.join(TEST_DATA, TEST_BACKGROUND), 0) @@ -3673,25 +3482,16 @@ def test_plantcv_background_subtraction_different_sizes(): assert np.sum(fgmask) > 0 -def test_plantcv_spatial_clustering_dbscan(): +@pytest.mark.parametrize("alg, min_size, max_size", [['DBSCAN', 10, None], + ['OPTICS', 100, 5000]] + ) +def test_plantcv_spatial_clustering(alg, min_size, max_size): cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_spatial_clustering_dbscan") os.mkdir(cache_dir) pcv.params.debug_outdir = cache_dir img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1) - pcv.params.debug = "print" - _ = pcv.spatial_clustering(img, algorithm="DBSCAN", min_cluster_size=10, max_distance=None) - pcv.params.debug = "plot" - spmask = pcv.spatial_clustering(img, algorithm="DBSCAN", min_cluster_size=10, max_distance=None) - assert len(spmask[1]) == 2 - - -def test_plantcv_spatial_clustering_optics(): - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_spatial_clustering_optics") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MULTI_MASK), -1) pcv.params.debug = None - spmask = pcv.spatial_clustering(img, algorithm="OPTICS", min_cluster_size=100, max_distance=5000) + spmask = pcv.spatial_clustering(img, algorithm=alg, min_cluster_size=min_size, max_distance=max_size) assert len(spmask[1]) == 2 @@ -3904,7 +3704,7 @@ def test_plantcv_morphology_fill_segments_with_stem(): pcv.params.debug = None _ = pcv.morphology.fill_segments(mask, obj, stem_obj) num_objects = len(pcv.outputs.observations['default']['leaf_area']['value']) - assert num_objects == 70 + assert num_objects == 69 def test_plantcv_morphology_segment_angle(): @@ -4057,9 +3857,9 @@ def test_plantcv_morphology_segment_insertion_angle(): pcv.params.debug = "print" _ = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 10) assert pcv.outputs.observations['default']['segment_insertion_angle']['value'][:6] == ['NA', 'NA', 'NA', - 24.956918822001636, - 50.7313343343401, - 56.427712102130734] + 24.956918822001636, + 50.7313343343401, + 56.427712102130734] def test_plantcv_morphology_segment_insertion_angle_bad_stem(): @@ -4171,6 +3971,12 @@ def test_plantcv_hyperspectral_read_data_approx_pseudorgb(): assert np.shape(array_data.array_data) == (1, 1600, 978) +def test_plantcv_hyperspectral_read_data_bad_interleave(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA_BAD_INTERLEAVE) + with pytest.raises(RuntimeError): + _ = pcv.hyperspectral.read_data(filename=spectral_filename) + + def test_plantcv_spectral_index_ndvi(): cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_hyperspectral_index_ndvi") os.mkdir(cache_dir) @@ -4887,78 +4693,209 @@ def test_plantcv_hyperspectral_inverse_covariance(): # ######################################## # Tests for the photosynthesis subpackage -# ######################################## -def test_plantcv_photosynthesis_read_dat(): - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_photosynthesis_read_dat") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir - pcv.params.debug = "plot" - fluor_filename = os.path.join(FLUOR_TEST_DATA, FLUOR_IMG) - _, _, _ = pcv.photosynthesis.read_cropreporter(filename=fluor_filename) - pcv.params.debug = "print" - fdark, fmin, fmax = pcv.photosynthesis.read_cropreporter(filename=fluor_filename) - assert np.sum(fmin) < np.sum(fmax) +# ######################################## +def psii_walz(var): + """Create and return synthetic psii dataarrays from walz""" + # create darkadapted + if var == 'darkadapted': + i = 0 + fmin = np.ones((10, 10), dtype='uint8') * ((i+15)*2) + fmax = np.ones((10, 10), dtype='uint8') * (200-i*15) + data = np.stack([fmin, fmax], axis=2) + + frame_nums = range(0, 2) + indf = ['F0','Fm'] + ps_da = xr.DataArray( + data=data[..., None], + dims=('x', 'y', 'frame_label', 'measurement'), + coords={'frame_label': indf, + 'frame_num': ('frame_label', frame_nums), + 'measurement': ['t0']}, + name='darkadapted' + ) + + # create lightadapted + elif var == 'lightadapted': + da_list = [] + measurement = [] + + for i in np.arange(1, 3): + indf = ['Fp', 'Fmp'] + fmin = np.ones((10, 10), dtype='uint8') * ((i+15)*2) + fmax = np.ones((10, 10), dtype='uint8') * (200-i*15) + data = np.stack([fmin, fmax], axis=2) + + lightadapted = xr.DataArray( + data=data[..., None], + dims=('x', 'y', 'frame_label', 'measurement'), + coords={'frame_label': indf, + 'frame_num': ('frame_label', range(0, 2))} + ) + + measurement.append((f't{i*40}')) + da_list.append(lightadapted) + + prop_idx = pd.Index(measurement) + ps_da = xr.concat(da_list, 'measurement') + ps_da.name = 'lightadapted' + ps_da.coords['measurement'] = prop_idx + + return(ps_da) + + +def test_plantcv_classes_psii_data(): + psii = pcv.PSII_data() + psii.add_data(psii_cropreporter('darkadapted')) + assert repr(psii) == "PSII variables defined:\ndarkadapted" + + +def test_plantcv_photosynthesis_read_cropreporter(): + # Test with debug = None + pcv.params.debug = None + fluor_filename = os.path.join(PHOTOSYNTHESIS_TEST_DATA, PHOTOSYNTHESIS_NPQ_IMG_INF) + ps = pcv.photosynthesis.read_cropreporter(filename=fluor_filename) + assert isinstance(ps, pcv.PSII_data) and ps.darkadapted.shape == (966, 1296, 21, 1) -def test_plantcv_photosynthesis_analyze_fvfm(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir - # filename = os.path.join(cache_dir, 'plantcv_fvfm_hist.png') - # Read in test data - fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1) - fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) - fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) - fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000, label="prefix") - # Test with debug = "plot" - pcv.params.debug = "plot" - fvfm_images = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) - assert len(fvfm_images) != 0 +def test_plantcv_photosynthesis_read_cropreporter_spc_only(tmpdir): + # Create a test tmp directory + cache_dir = tmpdir.mkdir("sub") + # Create dataset with only SPC + shutil.copyfile(os.path.join(PHOTOSYNTHESIS_TEST_DATA, PHOTOSYNTHESIS_NPQ_IMG_INF), + os.path.join(cache_dir, PHOTOSYNTHESIS_NPQ_IMG_INF)) + PHOTOSYNTHESIS_SPC_IMG_DAT = PHOTOSYNTHESIS_NPQ_IMG_INF.replace("HDR", "SPC") + PHOTOSYNTHESIS_SPC_IMG_DAT = PHOTOSYNTHESIS_SPC_IMG_DAT.replace("INF", "DAT") + shutil.copyfile(os.path.join(PHOTOSYNTHESIS_TEST_DATA, PHOTOSYNTHESIS_SPC_IMG_DAT), + os.path.join(cache_dir, PHOTOSYNTHESIS_SPC_IMG_DAT)) + # Test with debug = None + pcv.params.debug = None + fluor_filename = os.path.join(cache_dir, PHOTOSYNTHESIS_NPQ_IMG_INF) + ps = pcv.photosynthesis.read_cropreporter(filename=fluor_filename) + assert isinstance(ps, pcv.PSII_data) and ps.spectral.array_data.shape == (966, 1296, 3) + + +@pytest.mark.parametrize("mda, mlabels", + # test darkadapted control seq + [[psii_cropreporter("darkadapted"), None], + # test lightadapted control seq and measurement_labels arg + [psii_cropreporter("lightadapted"), ["Fq/Fm"]], + # test darkadapted walz + [psii_walz("darkadapted"), ["Fv/Fm"]], + # test lightadapted walz + [psii_walz("lightadapted"), [f't{i*40}' for i in np.arange(1, 3)]] + ] + ) +def test_plantcv_photosynthesis_analyze_yii(mda, mlabels): + # Test with debug = None + pcv.params.debug = None + _ = pcv.photosynthesis.analyze_yii(ps_da=mda, mask=ps_mask(), bins=100, + measurement_labels=mlabels, label="default") + if mlabels is None: + med = pcv.outputs.observations["default"]["yii_median_t0"]["value"] + pcv.outputs.clear() + assert med == 0.8 + elif "Fq/Fm" in mlabels: + med = pcv.outputs.observations["default"]["yii_median_Fq/Fm"]["value"] + pcv.outputs.clear() + assert med == 0.75 + elif "Fv/Fm" in mlabels: + med = pcv.outputs.observations["default"]["yii_median_Fv/Fm"]["value"] + pcv.outputs.clear() + assert med == float(np.around((200 - 30) / 200, decimals=4)) + elif "t40" in mlabels: + med = pcv.outputs.observations["default"]["yii_median_t40"]["value"] + pcv.outputs.clear() + assert med == float(np.around((185 - 32) / 185, decimals=4)) + + +@pytest.mark.parametrize("mlabels, tmask", + # test wrong mask shape + [[None, np.ones((2, 2))], + # test non binary mask + [None, np.random.random(ps_mask().shape)], + # test bad measurement_labels + [['f', 'm'], ps_mask()]]) +def test_plantcv_photosynthesis_analyze_yii_fatalerror(mlabels, tmask): + # Test with debug = None + pcv.params.debug = None + with pytest.raises(RuntimeError): + _ = pcv.photosynthesis.analyze_yii(ps_da=psii_cropreporter('darkadapted'), mask=tmask, + bins=100, measurement_labels=mlabels, label="default") -def test_plantcv_photosynthesis_analyze_fvfm_print_analysis_results(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir - fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1) - fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) - fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) - fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) - _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) - result_file = os.path.join(cache_dir, "results.txt") - pcv.print_results(result_file) - pcv.outputs.clear() - assert os.path.exists(result_file) +@pytest.mark.parametrize("mda_light, mda_dark, mlabels", + [ + # test cropreporter with measurement_labels + [psii_cropreporter('lightadapted'), psii_cropreporter('darkadapted'), ["Fq/Fm"]], + # test walz + [psii_walz('lightadapted'), psii_walz('darkadapted'), None] + ] + ) +def test_plantcv_photosynthesis_analyze_npq(mda_dark, mda_light, mlabels): + # Test with debug = None + pcv.params.debug = None + _ = pcv.photosynthesis.analyze_npq(ps_da_light=mda_light, ps_da_dark=mda_dark, + mask=ps_mask(), bins=100, measurement_labels=mlabels, label="prefix") + if mlabels is not None: + med = pcv.outputs.observations["prefix"]["npq_median_Fq/Fm"]["value"] + pcv.outputs.clear() + assert med == 0.25 + else: + med = pcv.outputs.observations["prefix"]["npq_median_t40"]["value"] + pcv.outputs.clear() + assert med == float(np.around(200 / 185 - 1, decimals=4)) + + +@pytest.mark.parametrize("mlabels, tmask", + # test wrong mask shape + [[None, np.ones((2, 2))], + # test non binary mask or not uint8 + [None, np.random.random(ps_mask().shape)], + # test bad measurement_labels + ['fm', ps_mask()]]) +def test_plantcv_photosynthesis_analyze_npq_fatalerror(mlabels, tmask): + # Test with debug = None + pcv.params.debug = None -def test_plantcv_photosynthesis_analyze_fvfm_bad_fdark(): - # Clear previous outputs - pcv.outputs.clear() - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_analyze_fvfm") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir - # Read in test data - fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FDARK), -1) - fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) - fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) - fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) - _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark + 3000, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) - check = pcv.outputs.observations['default']['fdark_passed_qc']['value'] is False - assert check + with pytest.raises(RuntimeError): + _ = pcv.photosynthesis.analyze_npq(ps_da_dark=psii_cropreporter('darkadapted'), ps_da_light=psii_cropreporter( + 'lightadapted'), mask=tmask, bins=100, measurement_labels=mlabels, label="default") -def test_plantcv_photosynthesis_analyze_fvfm_bad_input(): - fdark = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - fmin = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMIN), -1) - fmax = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMAX), -1) - fmask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_FMASK), -1) +@pytest.mark.parametrize("da", + [ + # test darkadapted + psii_cropreporter("darkadapted"), + # test lightadapted + psii_cropreporter("lightadapted") + ] + ) +def test_plantcv_photosynthesis_reassign_frame_labels(da): + # Test with debug = None + pcv.params.debug = None + _ = pcv.photosynthesis.reassign_frame_labels(ps_da=da, mask=ps_mask()) + + +@pytest.mark.parametrize("da, tmask", + [ + # test not PSII_data + [pcv.PSII_data(), ps_mask()], + # test input is dataarray + ['nope', ps_mask()], + # test input is dataarray with correct name + [psii_cropreporter('darkadapted').rename('test'), ps_mask()], + # test mask shape + [psii_cropreporter('darkadapted'), np.ones((2, 2))], + # test mask is binary + [psii_cropreporter('lightadapted'), np.random.random(ps_mask().shape)] + ] + ) +def test_plantcv_photosynthesis_reassign_frame_labels_fatalerror(da, tmask): + # Test with debug = None + pcv.params.debug = None with pytest.raises(RuntimeError): - _ = pcv.photosynthesis.analyze_fvfm(fdark=fdark, fmin=fmin, fmax=fmax, mask=fmask, bins=1000) + _, _ = pcv.photosynthesis.reassign_frame_labels(ps_da=da, mask=tmask) # ############################## @@ -5724,28 +5661,90 @@ def test_plantcv_transform_nonuniform_illumination_gray(): assert np.shape(corrected) == np.shape(gray_img) +def test_plantcv_transform_warp_default(): + pcv.params.debug = "plot" + img = create_test_img((12, 10, 3)) + refimg = create_test_img((12, 10, 3)) + pts = [(0, 0), (1, 0), (0, 3), (4, 4)] + refpts = [(0, 0), (1, 0), (0, 3), (4, 4)] + warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="default") + assert mat.shape == (3, 3) + + +def test_plantcv_transform_warp_lmeds(): + pcv.params.debug = "plot" + img = create_test_img((10, 10, 3)) + refimg = create_test_img((11, 11)) + pts = [(0, 0), (1, 0), (0, 3), (4, 4)] + refpts = [(0, 0), (1, 0), (0, 3), (4, 4)] + warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="lmeds") + assert mat.shape == (3, 3) + + +def test_plantcv_transform_warp_rho(): + pcv.params.debug = "plot" + img = create_test_img_bin((10, 10)) + refimg = create_test_img((11, 11)) + pts = [(0, 0), (1, 0), (0, 3), (4, 4)] + refpts = [(0, 0), (1, 0), (0, 3), (4, 4)] + warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="rho") + assert mat.shape == (3, 3) + + +def test_plantcv_transform_warp_ransac(): + pcv.params.debug = "plot" + img = create_test_img((100, 150)) + refimg = create_test_img((10, 15)) + pts = [(0, 0), (149, 0), (99, 149), (0, 99), (3, 3)] + refpts = [(0, 0), (0, 14), (9, 14), (0, 9), (3, 3)] + warped_img, mat = pcv.transform.warp(img, refimg, pts, refpts, method="ransac") + assert mat.shape == (3, 3) + + +@pytest.mark.parametrize("pts, refpts", [ + [[(0, 0)], [(0, 0), (0, 1)]], # different # of points provided for img and refimg + [[(0, 0)], [(0, 0)]], # not enough pairs of points provided + [[(0, 0), (0, 14), (9, 14), (0, 9), (3, 3)], + [(0, 0), (149, 0), (99, 149), (0, 99), (3, 3)]] # homography not able to be calculated (cannot converge) +]) +def test_plantcv_transform_warp_err(pts, refpts): + img = create_test_img((10, 15)) + refimg = create_test_img((100, 150)) + method = "rho" + with pytest.raises(RuntimeError): + pcv.transform.warp(img, refimg, pts, refpts, method=method) + + +def test_plantcv_transform_warp_align(): + img = create_test_img((10, 10, 3)) + refimg = create_test_img((11, 11)) + mat = np.array([[1.00000000e+00, 1.04238500e-15, -7.69185075e-16], + [1.44375646e-16, 1.00000000e+00, 0.00000000e+00], + [-5.41315251e-16, 1.78930521e-15, 1.00000000e+00]]) + warp_img = pcv.transform.warp_align(img=img, mat=mat, refimg=refimg) + assert warp_img.shape == (11, 11, 3) + + +def test_plantcv_transform_gamma_correct(): + # Read in test data + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MARKER)) + # Test + gamma_corrected = pcv.transform.gamma_correct(img=img, gamma=2, gain=1) + imgavg = np.average(img) + correctedavg = np.average(gamma_corrected) + assert correctedavg != imgavg + + # ############################## # Tests for the threshold subpackage # ############################## -def test_plantcv_threshold_binary(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_binary") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir +@pytest.mark.parametrize("objtype", ["dark", "light"]) +def test_plantcv_threshold_binary(objtype): # Read in test data gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) # Test with object type = dark pcv.params.debug = None - _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="dark") - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="light") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="light") - # Test with debug = None - pcv.params.debug = None - binary_img = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="light") + binary_img = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type=objtype) # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): # Assert that the image is binary @@ -5764,25 +5763,13 @@ def test_plantcv_threshold_binary_incorrect_object_type(): _ = pcv.threshold.binary(gray_img=gray_img, threshold=25, max_value=255, object_type="lite") -def test_plantcv_threshold_gaussian(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_gaussian") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir +@pytest.mark.parametrize("objtype", ["dark", "light"]) +def test_plantcv_threshold_gaussian(objtype): # Read in test data gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) # Test with object type = dark pcv.params.debug = None - _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="dark") - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="light") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="light") - # Test with debug = None - pcv.params.debug = None - binary_img = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="light") + binary_img = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type=objtype) # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): # Assert that the image is binary @@ -5801,25 +5788,13 @@ def test_plantcv_threshold_gaussian_incorrect_object_type(): _ = pcv.threshold.gaussian(gray_img=gray_img, max_value=255, object_type="lite") -def test_plantcv_threshold_mean(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_mean") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir +@pytest.mark.parametrize("objtype", ["dark", "light"]) +def test_plantcv_threshold_mean(objtype): # Read in test data gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) # Test with object type = dark pcv.params.debug = None - _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="dark") - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="light") - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="light") - # Test with debug = None - pcv.params.debug = None - binary_img = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="light") + binary_img = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type=objtype) # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): # Assert that the image is binary @@ -5838,25 +5813,13 @@ def test_plantcv_threshold_mean_incorrect_object_type(): _ = pcv.threshold.mean(gray_img=gray_img, max_value=255, object_type="lite") -def test_plantcv_threshold_otsu(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_otsu") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir +@pytest.mark.parametrize("objtype", ["dark", "light"]) +def test_plantcv_threshold_otsu(objtype): # Read in test data gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GREENMAG), -1) # Test with object set to light pcv.params.debug = None - _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type="light") - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type='dark') - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type='dark') - # Test with debug = None - pcv.params.debug = None - binary_img = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type='dark') + binary_img = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type=objtype) # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): # Assert that the image is binary @@ -5875,27 +5838,35 @@ def test_plantcv_threshold_otsu_incorrect_object_type(): _ = pcv.threshold.otsu(gray_img=gray_img, max_value=255, object_type="lite") -def test_plantcv_threshold_custom_range(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir +@pytest.mark.parametrize("channel,lower_thresh,upper_thresh", [["HSV", [0, 0, 0], [255, 255, 255]], + ["LAB", [0, 0, 0], [255, 255, 255]], + ["RGB", [0, 0, 0], [255, 255, 255]], + ["GRAY", [0], [255]]]) +def test_plantcv_threshold_custom_range_rgb(channel, lower_thresh, upper_thresh): # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + # Test with debug = None + pcv.params.debug = None + mask, binary_img = pcv.threshold.custom_range(img, lower_thresh=lower_thresh, upper_thresh=upper_thresh, + channel=channel) + # Assert that the output image has the dimensions of the input image + if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): + # Assert that the image is binary + if all([i == j] for i, j in zip(np.unique(binary_img), [0, 255])): + assert 1 + else: + assert 0 + else: + assert 0 + + +def test_plantcv_threshold_custom_range_grayscale(): + # Read in test data gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) - # Test with debug = "print" - pcv.params.debug = 'print' - # Test channel='gray' - _, _ = pcv.threshold.custom_range(img, lower_thresh=[0], upper_thresh=[255], channel='gray') - _, _ = pcv.threshold.custom_range(gray_img, lower_thresh=[0], upper_thresh=[255], channel='gray') - # Test channel='HSV' - _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0, 0], upper_thresh=[255, 255, 255], channel='HSV') - # Test channel='LAB' - _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0, 0], upper_thresh=[255, 255, 255], channel='LAB') - pcv.params.debug = 'plot' - # Test channel='RGB' - mask, binary_img = pcv.threshold.custom_range(img, lower_thresh=[0, 0, 0], upper_thresh=[255, 255, 255], - channel='RGB') + # Test with debug = None + pcv.params.debug = None + # # Test channel='gray' + mask, binary_img = pcv.threshold.custom_range(gray_img, lower_thresh=[0], upper_thresh=[255], channel='gray') # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): # Assert that the image is binary @@ -5908,10 +5879,6 @@ def test_plantcv_threshold_custom_range(): def test_plantcv_threshold_custom_range_bad_input_hsv(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) with pytest.raises(RuntimeError): @@ -5919,21 +5886,14 @@ def test_plantcv_threshold_custom_range_bad_input_hsv(): def test_plantcv_threshold_custom_range_bad_input_rgb(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data + pcv.params.debug = None img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) with pytest.raises(RuntimeError): _, _ = pcv.threshold.custom_range(img, lower_thresh=[0, 0], upper_thresh=[2, 2, 2, 2], channel='RGB') def test_plantcv_threshold_custom_range_bad_input_lab(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) with pytest.raises(RuntimeError): @@ -5941,10 +5901,6 @@ def test_plantcv_threshold_custom_range_bad_input_lab(): def test_plantcv_threshold_custom_range_bad_input_gray(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) with pytest.raises(RuntimeError): @@ -5952,37 +5908,23 @@ def test_plantcv_threshold_custom_range_bad_input_gray(): def test_plantcv_threshold_custom_range_bad_input_channel(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_range") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) with pytest.raises(RuntimeError): _, _ = pcv.threshold.custom_range(img, lower_thresh=[0], upper_thresh=[2], channel='CMYK') -def test_plantcv_threshold_saturation(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_saturation") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir +@pytest.mark.parametrize("channel", ["all", "any"]) +def test_plantcv_threshold_saturation(channel): # Read in test data rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel="all") - # Test with debug = "plot" - pcv.params.debug = "plot" - thresh = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel="any") - assert np.sum(thresh) == 920050455 and len(np.unique(thresh)) == 2 + # Test with debug = None + pcv.params.debug = None + thresh = pcv.threshold.saturation(rgb_img=rgb_img, threshold=254, channel=channel) + assert len(np.unique(thresh)) == 2 def test_plantcv_threshold_saturation_bad_input(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_saturation_bad_input") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir # Read in test data rgb_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) with pytest.raises(RuntimeError): @@ -5996,14 +5938,12 @@ def test_plantcv_threshold_triangle(): pcv.params.debug_outdir = cache_dir # Read in test data gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) - # Test with debug = "print" - pcv.params.debug = "print" + + pcv.params.debug = None _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="dark", xstep=10) - # Test with debug = "plot" pcv.params.debug = "plot" _ = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="light", xstep=10) - # Test with debug = None - pcv.params.debug = None + pcv.params.debug = "print" binary_img = pcv.threshold.triangle(gray_img=gray_img, max_value=255, object_type="light", xstep=10) # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(binary_img), TEST_GRAY_DIM)): @@ -6024,10 +5964,8 @@ def test_plantcv_threshold_triangle_incorrect_object_type(): def test_plantcv_threshold_texture(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_threshold_texture") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir + # Test with debug = None + pcv.params.debug = None gray_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY_SMALL), -1) binary_img = pcv.threshold.texture(gray_img, ksize=6, threshold=7, offset=3, texture_method='dissimilarity', borders='nearest', max_value=255) @@ -6042,59 +5980,115 @@ def test_plantcv_threshold_texture(): assert 0 +def create_test_img(sz_img): + img = np.random.randint(np.prod(sz_img), size=sz_img) * 255 + img = img.astype(np.uint8) + return img + + +def create_test_img_bin(sz_img): + img = np.zeros(sz_img) + img[3:7, 2:8] = 1 + return img + + +@pytest.mark.parametrize("bad_type", ["native", "nan", "inf"]) +def test_plantcv_threshold_mask_bad(bad_type): + # Create a synthetic bad image + bad_img = np.reshape(np.random.rand(25), (5, 5)) + bad_img[2, 2] = np.inf + bad_img[2, 3] = np.nan + sz = np.shape(bad_img) + pcv.params.debug = None + mask = pcv.threshold.mask_bad(bad_img, bad_type=bad_type) + assert((np.shape(mask) == sz) and (len(np.unique(mask)) == 2)) + + +def test_plantcv_threshold_mask_bad_native_bad_input(): + # Create a synthetic bad image + bad_img = np.reshape(np.random.rand(25), (5, 5)) + sz = np.shape(bad_img) + mask10 = pcv.threshold.mask_bad(bad_img, bad_type='native') + + assert mask10.all() == np.zeros(sz, dtype='uint8').all() + + +def test_plantcv_threshold_mask_bad_nan_bad_input(): + # Create a synthetic bad image + bad_img = np.reshape(np.random.rand(25), (5, 5)) + bad_img[2, 2] = np.inf + sz = np.shape(bad_img) + mask11 = pcv.threshold.mask_bad(bad_img, bad_type='nan') + + assert mask11.all() == np.zeros(sz, dtype='uint8').all() + + +def test_plantcv_threshold_mask_bad_input_color_img(): + # Read in test data + bad_img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) + with pytest.raises(RuntimeError): + pcv.threshold.mask_bad(bad_img, bad_type='nan') + + # ################################### # Tests for the visualize subpackage # ################################### def test_plantcv_visualize_auto_threshold_methods_bad_input(): - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_threshold_methods") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir + # Test with debug = None + pcv.params.debug = None img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) with pytest.raises(RuntimeError): _ = pcv.visualize.auto_threshold_methods(gray_img=img) def test_plantcv_visualize_auto_threshold_methods(): - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_auto_threshold_methods") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir + # Test with debug = None + pcv.params.debug = None img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_GRAY), -1) - pcv.params.debug = "print" - _ = pcv.visualize.auto_threshold_methods(gray_img=img) - pcv.params.debug = "plot" labeled_imgs = pcv.visualize.auto_threshold_methods(gray_img=img) assert len(labeled_imgs) == 5 and np.shape(labeled_imgs[0])[0] == np.shape(img)[0] -def test_plantcv_visualize_pseudocolor(): - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_pseudocolor") - os.mkdir(cache_dir) +@pytest.mark.parametrize("debug,axes", [["print", True], ["plot", False]]) +def test_plantcv_visualize_pseudocolor(debug, axes, tmpdir): + # Create a tmp directory + cache_dir = tmpdir.mkdir("sub") pcv.params.debug_outdir = cache_dir + # Input image img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) - contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") - obj_contour = contours_npz['arr_0'] - filename = os.path.join(cache_dir, 'plantcv_pseudo_image.png') - # Test with debug = "print" - pcv.params.debug = "print" - _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) - _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) + r, c = img.shape + # generate 200 "bad" pixels + mask_bad = np.zeros((r, c), dtype=np.uint8) + mask_bad = np.reshape(mask_bad, (-1, 1)) + mask_bad[0:100] = 255 + mask_bad = np.reshape(mask_bad, (r, c)) + # Debug mode + pcv.params.debug = debug + pseudo_img = pcv.visualize.pseudocolor(gray_img=img, mask=None, title="Pseudocolored image", axes=axes, + bad_mask=mask_bad) + # Assert that the output image has the dimensions of the input image + assert all([i == j] for i, j in zip(np.shape(pseudo_img), TEST_BINARY_DIM)) - pimg = pcv.visualize.pseudocolor(gray_img=img, mask=mask, min_value=10, max_value=200) - pcv.print_image(pimg, filename) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="image") - _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="image", title="customized title") - _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) - _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="black", obj=obj_contour, axes=False, - colorbar=False) - _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="image", obj=obj_contour, obj_padding=15) - _ = pcv.visualize.pseudocolor(gray_img=img, mask=None, axes=False, colorbar=False) + +@pytest.mark.parametrize("bkgrd,axes,pad", [["image", True, "auto"], ["white", False, 1], ["black", True, "auto"]]) +def test_plantcv_visualize_pseudocolor_mask(bkgrd, axes, pad): # Test with debug = None pcv.params.debug = None - _ = pcv.visualize.pseudocolor(gray_img=img, mask=None) - pseudo_img = pcv.visualize.pseudocolor(gray_img=img, mask=mask, background="white") + # Input image + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Input mask + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_BINARY), -1) + # Input contours + contours_npz = np.load(os.path.join(TEST_DATA, TEST_INPUT_CONTOURS), encoding="latin1") + obj_contour = contours_npz['arr_0'] + r, c = img.shape + # generate 200 "bad" pixels + mask_bad = np.zeros((r, c), dtype=np.uint8) + mask_bad = np.reshape(mask_bad, (-1, 1)) + mask_bad[0:100] = 255 + mask_bad = np.reshape(mask_bad, (r, c)) + pseudo_img = pcv.visualize.pseudocolor(gray_img=img, obj=obj_contour, mask=mask, background=bkgrd, + bad_mask=mask_bad, title="Pseudocolored image", axes=axes, obj_padding=pad) # Assert that the output image has the dimensions of the input image if all([i == j] for i, j in zip(np.shape(pseudo_img), TEST_BINARY_DIM)): assert 1 @@ -6133,26 +6127,21 @@ def test_plantcv_visualize_pseudocolor_bad_padding(): _ = pcv.visualize.pseudocolor(gray_img=img, mask=mask, obj=obj_contour, obj_padding="pink") -def test_plantcv_visualize_colorize_masks(): - # Test cache directory - cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_naive_bayes_classifier") - os.mkdir(cache_dir) - pcv.params.debug_outdir = cache_dir - # Read in test data - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) - _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], - colors=[(0, 0, 0), (1, 1, 1)]) - # Test with debug = "plot" - pcv.params.debug = "plot" - _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], - colors=[(0, 0, 0), (1, 1, 1)]) +def test_plantcv_visualize_pseudocolor_bad_mask(): + # Test with debug = None + pcv.params.debug = None + + +@pytest.mark.parametrize('colors', [['red', 'blue'], [(0, 0, 255), (255, 0, 0)]]) +def test_plantcv_visualize_colorize_masks(colors): # Test with debug = None pcv.params.debug = None - colored_img = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], - colors=['red', 'blue']) + # Create test data + mask1 = np.zeros((100, 100), dtype=np.uint8) + mask2 = np.copy(mask1) + mask1[0:50, 0:50] = 255 + mask2[50:100, 50:100] = 255 + colored_img = pcv.visualize.colorize_masks(masks=[mask1, mask2], colors=colors) # Assert that the output image has the dimensions of the input image assert not np.average(colored_img) == 0 @@ -6163,23 +6152,30 @@ def test_plantcv_visualize_colorize_masks_bad_input_empty(): def test_plantcv_visualize_colorize_masks_bad_input_mismatch_number(): - # Read in test data - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + # Create test data + mask1 = np.zeros((100, 100), dtype=np.uint8) + mask2 = np.copy(mask1) + mask1[0:50, 0:50] = 255 + mask2[50:100, 50:100] = 255 with pytest.raises(RuntimeError): - _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], colors=['red', 'green', 'blue']) + _ = pcv.visualize.colorize_masks(masks=[mask1, mask2], colors=['red', 'green', 'blue']) def test_plantcv_visualize_colorize_masks_bad_color_input(): - # Read in test data - img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR)) - # Test with debug = "print" - pcv.params.debug = "print" - mask = pcv.naive_bayes_classifier(rgb_img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS)) + # Create test data + mask1 = np.zeros((100, 100), dtype=np.uint8) + mask2 = np.copy(mask1) + mask1[0:50, 0:50] = 255 + mask2[50:100, 50:100] = 255 with pytest.raises(RuntimeError): - _ = pcv.visualize.colorize_masks(masks=[mask['plant'], mask['background']], colors=['red', 1.123]) + _ = pcv.visualize.colorize_masks(masks=[mask1, mask2], colors=['red', 1.123]) + + +def test_plantcv_visualize_colorize_label_img(): + label_img = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + pcv.params.debug = None + colored_img = pcv.visualize.colorize_label_img(label_img) + assert (colored_img.shape[0:-1] == label_img.shape) and colored_img.shape[-1] == 3 @pytest.mark.parametrize("bins,lb,ub,title", [[200, 0, 255, "Include Title"], [100, None, None, None]]) @@ -6234,6 +6230,46 @@ def test_plantcv_visualize_histogram_array(): _ = pcv.visualize.histogram(img=img[0, :]) +@pytest.mark.parametrize("wavelengths", [[], [390, 500, 640, 992, 990]]) +def test_plantcv_visualize_hyper_histogram(wavelengths): + # Test with debug = None + pcv.params.debug = None + + # Read in test data + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array = pcv.hyperspectral.read_data(filename=spectral_filename) + mask = np.ones((array.lines, array.samples)) + + fig_hist = pcv.visualize.hyper_histogram(array, mask, wvlengths=wavelengths, title="Hyper Histogram Test") + assert isinstance(fig_hist, ggplot) + + +def test_plantcv_visualize_hyper_histogram_wv_out_range(): + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array = pcv.hyperspectral.read_data(filename=spectral_filename) + with pytest.raises(RuntimeError): + _ = pcv.visualize.hyper_histogram(array, wvlengths=[200, 550]) + + +def test_plantcv_visualize_hyper_histogram_extreme_wvs(): + # Test with debug = None + pcv.params.debug = None + + # Read in test data + spectral_filename = os.path.join(HYPERSPECTRAL_TEST_DATA, HYPERSPECTRAL_DATA) + array = pcv.hyperspectral.read_data(filename=spectral_filename) + mask = np.ones((array.lines, array.samples)) + + wv_keys = list(array.wavelength_dict.keys()) + wavelengths = [250, 270, 1800, 2500] + # change first 4 keys + for (k_, k) in zip(wv_keys[0:5], wavelengths): + array.wavelength_dict[k] = array.wavelength_dict.pop(k_) + array.min_wavelength, array.max_wavelength = min(array.wavelength_dict), max(array.wavelength_dict) + fig_hist = pcv.visualize.hyper_histogram(array, mask, wvlengths=wavelengths) + assert isinstance(fig_hist, ggplot) + + def test_plantcv_visualize_clustered_contours(): # Test cache directory cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_plot_hist") @@ -6253,13 +6289,13 @@ def test_plantcv_visualize_clustered_contours(): # Reset the saved color scale (can be saved between tests) pcv.params.saved_color_scale = None _ = pcv.visualize.clustered_contours(img=img1, grouped_contour_indices=cluster, roi_objects=objs, - roi_obj_hierarchy=obj_hierarchy, bounding=False) + roi_obj_hierarchy=obj_hierarchy, bounding=False) # Test in print mode pcv.params.debug = "print" # Reset the saved color scale (can be saved between tests) pcv.params.saved_color_scale = None cluster_img = pcv.visualize.clustered_contours(img=img, grouped_contour_indices=cluster, roi_objects=objs, - roi_obj_hierarchy=obj_hierarchy, nrow=2, ncol=2, bounding=True) + roi_obj_hierarchy=obj_hierarchy, nrow=2, ncol=2, bounding=True) assert np.sum(cluster_img) > np.sum(img) @@ -6396,9 +6432,28 @@ def test_plantcv_visualize_click_count(): counter.onclick(e2_) assert len(counter.events) == 4 + +@pytest.mark.parametrize("num,expected", [[100, 35], [30, 33]]) +def test_plantcv_visualize_size(num, expected): + pcv.params.debug = None + img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_LEAF_MASK), -1) + visualization = pcv.visualize.obj_sizes(img=img, mask=img, num_objects=num) + # Output unique colors are the 32 objects, the gray text, the black background, and white unlabeled leaves + assert len(np.unique(visualization.reshape(-1, visualization.shape[2]), axis=0)) == expected + + +@pytest.mark.parametrize("title", ["Include Title", None]) +def test_plantcv_visualize_obj_size_ecdf(title): + pcv.params.debug = None + mask = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_MASK), -1) + fig_ecdf = plantcv.plantcv.visualize.obj_size_ecdf(mask=mask, title=title) + assert isinstance(fig_ecdf, ggplot) + + # ############################## # Tests for the utils subpackage # ############################## + def test_plantcv_utils_json2csv(): # Test cache directory cache_dir = os.path.join(TEST_TMPDIR, "test_plantcv_utils_json2csv") From 2fbdd9ee11505c25c08d1ab53e624d17b7d668a5 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Tue, 21 Sep 2021 10:28:15 -0500 Subject: [PATCH 033/178] revert back to integer conversion code debugged testing image, so changing back to int conversion on centers --- plantcv/plantcv/detect_discs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plantcv/plantcv/detect_discs.py b/plantcv/plantcv/detect_discs.py index 280378181..f5975f400 100644 --- a/plantcv/plantcv/detect_discs.py +++ b/plantcv/plantcv/detect_discs.py @@ -38,9 +38,8 @@ def detect_discs(bin_img, ecc_thresh=0): for i,obj in enumerate(obj_measures): if obj.eccentricity < ecc_thresh: # Convert coord values to int - #coords = tuple(map(int, obj.centroid)) - #discs_coor.append(coords) - discs_coor.append(obj.centroid) + coords = tuple(map(int, obj.centroid)) + discs_coor.append(coords) discs_mask = discs_mask + (labeled_img == i+1) _debug(visual=255*discs_mask, filename=os.path.join(params.debug_outdir, From dbfe6423a032b2f240abd04cb019802540e57aa0 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Tue, 21 Sep 2021 10:44:10 -0500 Subject: [PATCH 034/178] Create interactive_ClickCount_tutorial.md binder tutorials don't exist yet, so i might need to rename links once I actually create them --- docs/tutorials/interactive_ClickCount_tutorial.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 docs/tutorials/interactive_ClickCount_tutorial.md diff --git a/docs/tutorials/interactive_ClickCount_tutorial.md b/docs/tutorials/interactive_ClickCount_tutorial.md new file mode 100644 index 000000000..5dbdbfdd3 --- /dev/null +++ b/docs/tutorials/interactive_ClickCount_tutorial.md @@ -0,0 +1,6 @@ +## Tutorial: Seed Analysis Workflow + + +[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/danforthcenter/plantcv-binder.git/master?filepath=notebooks/interactive_seed_count/interactive_seed_count_workflow.ipynb) Check out our interactive seed analysis tutorial! + + From f7ec1bf284a669aabe6da880c37142f4081e8a46 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Tue, 21 Sep 2021 10:48:53 -0500 Subject: [PATCH 035/178] add seed count tutorial --- docs/tutorials.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/tutorials.md b/docs/tutorials.md index 71e95958c..1b8985fb3 100644 --- a/docs/tutorials.md +++ b/docs/tutorials.md @@ -278,6 +278,22 @@ + +
    +
    + + Interactive Seed Count tutorial + +
    +
    Interactive Seed Count Tutorial
    +
    +
    + Tags: seed, seed analysis, human-in-the-loop, RGB, seed count, camilina +
    +
    + +
    +